Merge "Add permission for home sound effect exception" into sc-dev
diff --git a/apps/CameraITS/build/envsetup.sh b/apps/CameraITS/build/envsetup.sh
index eb69844..c52e57e 100644
--- a/apps/CameraITS/build/envsetup.sh
+++ b/apps/CameraITS/build/envsetup.sh
@@ -61,7 +61,7 @@
-for M in sensor_fusion_utils camera_properties_utils capture_request_utils cv2_image_processing_utils image_processing_utils its_session_utils scene_change_utils target_exposure_utils
+for M in sensor_fusion_utils camera_properties_utils capture_request_utils opencv_processing_utils image_processing_utils its_session_utils scene_change_utils target_exposure_utils
do
python "utils/$M.py" 2>&1 | grep -q "OK" || \
echo ">> Unit test for $M failed" >&2
diff --git a/apps/CameraITS/tests/its_base_test.py b/apps/CameraITS/tests/its_base_test.py
index 992d086..9c488d6 100644
--- a/apps/CameraITS/tests/its_base_test.py
+++ b/apps/CameraITS/tests/its_base_test.py
@@ -35,8 +35,7 @@
# 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_solid_color_test_pattern', 31],
- ['test_test_patterns', 30],
+ 'scene0': [['test_test_patterns', 30],
['test_tonemap_curve', 30]],
'scene1_1': [['test_ae_precapture_trigger', 28],
['test_channel_saturation', 29]],
@@ -189,8 +188,8 @@
# Determine which test are not yet mandated for first api level.
tests = NOT_YET_MANDATED[scene]
for [test, first_api_level_mandated] in tests:
- logging.debug('First API level test MANDATED: %d',
- first_api_level_mandated)
+ logging.debug('First API level %s MANDATED: %d',
+ test, first_api_level_mandated)
if first_api_level < first_api_level_mandated:
not_yet_mandated[scene].append(test)
return not_yet_mandated
diff --git a/apps/CameraITS/tests/scene0/test_burst_capture.py b/apps/CameraITS/tests/scene0/test_burst_capture.py
index aeb226f..c8a6be0 100644
--- a/apps/CameraITS/tests/scene0/test_burst_capture.py
+++ b/apps/CameraITS/tests/scene0/test_burst_capture.py
@@ -11,6 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verify capture burst of full size images is fast enough to not timeout.
+"""
import logging
import os
diff --git a/apps/CameraITS/tests/scene0/test_capture_result_dump.py b/apps/CameraITS/tests/scene0/test_capture_result_dump.py
index e00df1b..9e175a3 100644
--- a/apps/CameraITS/tests/scene0/test_capture_result_dump.py
+++ b/apps/CameraITS/tests/scene0/test_capture_result_dump.py
@@ -11,6 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""CameraITS test that a capture result is returned from a manual capture.
+"""
import pprint
from mobly import test_runner
diff --git a/apps/CameraITS/tests/scene0/test_gyro_bias.py b/apps/CameraITS/tests/scene0/test_gyro_bias.py
index 20c0af9..70904a5 100644
--- a/apps/CameraITS/tests/scene0/test_gyro_bias.py
+++ b/apps/CameraITS/tests/scene0/test_gyro_bias.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verify if the gyro has stable output when device is stationary."""
import logging
import os
diff --git a/apps/CameraITS/tests/scene0/test_jitter.py b/apps/CameraITS/tests/scene0/test_jitter.py
index 8cf82d8..3743cb6 100644
--- a/apps/CameraITS/tests/scene0/test_jitter.py
+++ b/apps/CameraITS/tests/scene0/test_jitter.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""CameraITS test to measure jitter in camera timestamps."""
import logging
import os.path
diff --git a/apps/CameraITS/tests/scene0/test_metadata.py b/apps/CameraITS/tests/scene0/test_metadata.py
index a808d63..0ca31af 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""CameraITS test to verify metadata entries."""
import logging
import math
diff --git a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
index 76bfb4d..863ffbc 100644
--- a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
+++ b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
@@ -11,6 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""CameraITS test to see sensitivity param applied properly in burst or not.
+"""
from mobly import test_runner
diff --git a/apps/CameraITS/tests/scene0/test_read_write.py b/apps/CameraITS/tests/scene0/test_read_write.py
index 98359be..e8b514a 100644
--- a/apps/CameraITS/tests/scene0/test_read_write.py
+++ b/apps/CameraITS/tests/scene0/test_read_write.py
@@ -11,6 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""CameraITS test that the device will write/read correct exp/gain values.
+"""
import logging
import os.path
diff --git a/apps/CameraITS/tests/scene0/test_sensor_events.py b/apps/CameraITS/tests/scene0/test_sensor_events.py
index fb4a843..e8afbb2 100644
--- a/apps/CameraITS/tests/scene0/test_sensor_events.py
+++ b/apps/CameraITS/tests/scene0/test_sensor_events.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""CameraITS test for sensor events."""
import logging
import time
diff --git a/apps/CameraITS/tests/scene0/test_solid_color_test_pattern.py b/apps/CameraITS/tests/scene0/test_solid_color_test_pattern.py
index 3edcc2f..b953f4e 100644
--- a/apps/CameraITS/tests/scene0/test_solid_color_test_pattern.py
+++ b/apps/CameraITS/tests/scene0/test_solid_color_test_pattern.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""CameraITS test to check solid color test pattern generation."""
import logging
import os
@@ -39,6 +40,9 @@
_BLUE = {'color': 'BLUE', 'RGGB': (_OFF, _OFF, _OFF, _SAT), 'RGB': (0, 0, 1)}
_COLORS_CHECKED_RGB = (_BLACK, _WHITE, _RED, _GREEN, _BLUE)
_COLORS_CHECKED_MONO = (_BLACK, _WHITE)
+_COLORS_CHECKED_UPGRADE = (_BLACK,)
+_FULL_CHECK_FIRST_API_LEVEL = 31
+_SOLID_COLOR_TEST_PATTERN = 1
def check_solid_color(img, exp_values):
@@ -66,7 +70,7 @@
"""Solid Color test pattern generation test.
Test: Capture frame for the SOLID_COLOR test pattern with the values set
- and check RAW image matches request.
+ and check YUV image matches request.
android.sensor.testPatternMode
0: OFF
@@ -83,14 +87,26 @@
hidden_physical_id=self.hidden_physical_id) as cam:
props = cam.get_camera_properties()
props = cam.override_with_hidden_physical_camera_props(props)
- camera_properties_utils.skip_unless(
- camera_properties_utils.solid_color_test_pattern(props))
- # Handle MONO cameras
- if camera_properties_utils.mono_camera(props):
- colors_checked = _COLORS_CHECKED_MONO
+ # Determine patterns to check based on API level
+ first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
+ if first_api_level >= _FULL_CHECK_FIRST_API_LEVEL:
+ if camera_properties_utils.mono_camera(props):
+ colors_checked = _COLORS_CHECKED_MONO
+ else:
+ colors_checked = _COLORS_CHECKED_RGB
else:
- colors_checked = _COLORS_CHECKED_RGB
+ colors_checked = _COLORS_CHECKED_UPGRADE
+
+ # Determine if test is run or skipped
+ available_patterns = props['android.sensor.availableTestPatternModes']
+ if cam.is_camera_privacy_mode_supported():
+ if _SOLID_COLOR_TEST_PATTERN not in available_patterns:
+ raise AssertionError(
+ 'SOLID_COLOR not in android.sensor.availableTestPatternModes.')
+ else:
+ camera_properties_utils.skip_unless(
+ _SOLID_COLOR_TEST_PATTERN in available_patterns)
# Take extra frames if no per-frame control
if camera_properties_utils.per_frame_control(props):
diff --git a/apps/CameraITS/tests/scene0/test_test_patterns.py b/apps/CameraITS/tests/scene0/test_test_patterns.py
index 2dcd459..ea1e435 100644
--- a/apps/CameraITS/tests/scene0/test_test_patterns.py
+++ b/apps/CameraITS/tests/scene0/test_test_patterns.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""CameraITS test to check test patterns generation."""
import logging
import os
diff --git a/apps/CameraITS/tests/scene0/test_tonemap_curve.py b/apps/CameraITS/tests/scene0/test_tonemap_curve.py
index 0416f93..981b79b 100644
--- a/apps/CameraITS/tests/scene0/test_tonemap_curve.py
+++ b/apps/CameraITS/tests/scene0/test_tonemap_curve.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""CameraITS test for tonemap curve with sensor test pattern."""
import logging
import os
diff --git a/apps/CameraITS/tests/scene0/test_unified_timestamps.py b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
index 340321e..f7e5991 100644
--- a/apps/CameraITS/tests/scene0/test_unified_timestamps.py
+++ b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""CameraITS test for unified timestamp for image and motion sensor events."""
import logging
import time
diff --git a/apps/CameraITS/tests/scene0/test_vibration_restriction.py b/apps/CameraITS/tests/scene0/test_vibration_restriction.py
index 4146417..45c82e9 100644
--- a/apps/CameraITS/tests/scene0/test_vibration_restriction.py
+++ b/apps/CameraITS/tests/scene0/test_vibration_restriction.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Test to see if vibrations can be muted by camera-audio-restriction API."""
import logging
import math
diff --git a/apps/CameraITS/tests/scene1_1/test_3a.py b/apps/CameraITS/tests/scene1_1/test_3a.py
index df83687..195cd67 100644
--- a/apps/CameraITS/tests/scene1_1/test_3a.py
+++ b/apps/CameraITS/tests/scene1_1/test_3a.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifiers 3A converges with gray chart scene."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_ae_af.py b/apps/CameraITS/tests/scene1_1/test_ae_af.py
index ded6c51..c383200 100644
--- a/apps/CameraITS/tests/scene1_1/test_ae_af.py
+++ b/apps/CameraITS/tests/scene1_1/test_ae_af.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies AE and AF can run independently."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py b/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
index 963b629..dae3c05 100644
--- a/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
+++ b/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies AE state machine when using precapture trigger."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py b/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
index 922cea9..72c791d 100644
--- a/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
+++ b/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies auto and manual captures are similar with same scene."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_black_white.py b/apps/CameraITS/tests/scene1_1/test_black_white.py
index ed9f66f..2421cc9 100644
--- a/apps/CameraITS/tests/scene1_1/test_black_white.py
+++ b/apps/CameraITS/tests/scene1_1/test_black_white.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies camera will produce full black & full white images."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py b/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
index eff3327..b43067c 100644
--- a/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
+++ b/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies manual burst capture consistency."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_capture_result.py b/apps/CameraITS/tests/scene1_1/test_capture_result.py
index 8980874..73dfe5a 100644
--- a/apps/CameraITS/tests/scene1_1/test_capture_result.py
+++ b/apps/CameraITS/tests/scene1_1/test_capture_result.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies valid data return from CaptureResult objects."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py b/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
index 4adb6cd..69cc33f 100644
--- a/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
+++ b/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies RAW streams are not croppable."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_crop_regions.py b/apps/CameraITS/tests/scene1_1/test_crop_regions.py
index 2529e4e..148d863 100644
--- a/apps/CameraITS/tests/scene1_1/test_crop_regions.py
+++ b/apps/CameraITS/tests/scene1_1/test_crop_regions.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies android.scaler.cropRegion param works."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py b/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
index 9dbb861..8af5aed 100644
--- a/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
+++ b/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies the DNG RAW model parameters are correct."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_ev_compensation_advanced.py b/apps/CameraITS/tests/scene1_1/test_ev_compensation_advanced.py
index f6bfa44..e51f4fa 100644
--- a/apps/CameraITS/tests/scene1_1/test_ev_compensation_advanced.py
+++ b/apps/CameraITS/tests/scene1_1/test_ev_compensation_advanced.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies EV compensation is applied."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_ev_compensation_basic.py b/apps/CameraITS/tests/scene1_1/test_ev_compensation_basic.py
index 471d4ab..5c892c1 100644
--- a/apps/CameraITS/tests/scene1_1/test_ev_compensation_basic.py
+++ b/apps/CameraITS/tests/scene1_1/test_ev_compensation_basic.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies EV compensation is applied."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_exposure.py b/apps/CameraITS/tests/scene1_1/test_exposure.py
index b8791d2..4fd34ca 100644
--- a/apps/CameraITS/tests/scene1_1/test_exposure.py
+++ b/apps/CameraITS/tests/scene1_1/test_exposure.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies correct exposure control."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_jpeg.py b/apps/CameraITS/tests/scene1_1/test_jpeg.py
index a89aca5..2c11d29 100644
--- a/apps/CameraITS/tests/scene1_1/test_jpeg.py
+++ b/apps/CameraITS/tests/scene1_1/test_jpeg.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies converted YUV images & device JPEG images look the same."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_latching.py b/apps/CameraITS/tests/scene1_1/test_latching.py
index 08655e6..158bb0f 100644
--- a/apps/CameraITS/tests/scene1_1/test_latching.py
+++ b/apps/CameraITS/tests/scene1_1/test_latching.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies settings latch on the correct frame."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_linearity.py b/apps/CameraITS/tests/scene1_1/test_linearity.py
index 4b03792..885311b 100644
--- a/apps/CameraITS/tests/scene1_1/test_linearity.py
+++ b/apps/CameraITS/tests/scene1_1/test_linearity.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies linear behavior in exposure/gain space."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_locked_burst.py b/apps/CameraITS/tests/scene1_1/test_locked_burst.py
index 6334601..db72259 100644
--- a/apps/CameraITS/tests/scene1_1/test_locked_burst.py
+++ b/apps/CameraITS/tests/scene1_1/test_locked_burst.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies YUV image consistency with AE and AWB locked."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py b/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
index d47920c..6c8d0e8 100644
--- a/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
+++ b/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies sub-cameras have similar RGB values for gray patch."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_param_color_correction.py b/apps/CameraITS/tests/scene1_1/test_param_color_correction.py
index dc37d7b..b6cf66f 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_color_correction.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_color_correction.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies color correction parameter settings."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py b/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
index 920b5c3..7000bc2 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies android.sensor.exposureTime parameter."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py b/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
index d075fd5..6fede89 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies android.flash.mode parameters is applied when set."""
import logging
diff --git a/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py b/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
index 4567025..b605757 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies android.noiseReduction.mode parameters is applied when set."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_param_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_param_sensitivity.py
index 7308801..2c0446b 100644
--- a/apps/CameraITS/tests/scene1_2/test_param_sensitivity.py
+++ b/apps/CameraITS/tests/scene1_2/test_param_sensitivity.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies android.sensor.sensitivity parameter is applied."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_param_shading_mode.py b/apps/CameraITS/tests/scene1_2/test_param_shading_mode.py
index a39fcd4..e3143f4 100644
--- a/apps/CameraITS/tests/scene1_2/test_param_shading_mode.py
+++ b/apps/CameraITS/tests/scene1_2/test_param_shading_mode.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies android.shading.mode parameter is applied."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py b/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
index 4c487df..c2e4c4d 100644
--- a/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
+++ b/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifiers android.tonemap.mode parameter applies."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_post_raw_sensitivity_boost.py b/apps/CameraITS/tests/scene1_2/test_post_raw_sensitivity_boost.py
index 2e9d026..40bc979 100644
--- a/apps/CameraITS/tests/scene1_2/test_post_raw_sensitivity_boost.py
+++ b/apps/CameraITS/tests/scene1_2/test_post_raw_sensitivity_boost.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies post RAW sensitivity boost."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
index 8a58f6f..c52f977 100644
--- a/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
+++ b/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies RAW sensitivity burst."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_raw_exposure.py b/apps/CameraITS/tests/scene1_2/test_raw_exposure.py
index 209c83a..f00e661 100644
--- a/apps/CameraITS/tests/scene1_2/test_raw_exposure.py
+++ b/apps/CameraITS/tests/scene1_2/test_raw_exposure.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies exposure times on RAW images."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
index 307d620..0a57945 100644
--- a/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
+++ b/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies sensitivities on RAW images."""
import logging
@@ -22,9 +23,9 @@
import its_base_test
import camera_properties_utils
import capture_request_utils
-import cv2_image_processing_utils
import image_processing_utils
import its_session_utils
+import opencv_processing_utils
GR_PLANE_IDX = 1 # GR plane index in RGGB data
IMG_STATS_GRID = 9 # Center 11.11%
@@ -77,7 +78,7 @@
sens_step = (sens_max - sens_min) // NUM_SENS_STEPS
# Skip AF if TELE camera
- if camera_fov <= cv2_image_processing_utils.FOV_THRESH_TELE:
+ if camera_fov <= opencv_processing_utils.FOV_THRESH_TELE:
s_ae, e_ae, _, _, _ = cam.do_3a(do_af=False, get_results=True)
f_dist = 0
else:
diff --git a/apps/CameraITS/tests/scene1_2/test_reprocess_noise_reduction.py b/apps/CameraITS/tests/scene1_2/test_reprocess_noise_reduction.py
index 47d547b..f749d77 100644
--- a/apps/CameraITS/tests/scene1_2/test_reprocess_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1_2/test_reprocess_noise_reduction.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies android.noiseReduction.mode applied for reprocessing reqs."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_tonemap_sequence.py b/apps/CameraITS/tests/scene1_2/test_tonemap_sequence.py
index efdba29..10e91fc 100644
--- a/apps/CameraITS/tests/scene1_2/test_tonemap_sequence.py
+++ b/apps/CameraITS/tests/scene1_2/test_tonemap_sequence.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies shots with different tonemap curves."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py b/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
index a68d74f..b1f1b8a 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies YUV & JPEG image captures have similar brightness."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_plus_dng.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_dng.py
index d55ab93..c632fc9 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_plus_dng.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_plus_dng.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies single capture of both DNG and YUV."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_plus_jpeg.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_jpeg.py
index c40e486..850c139 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_plus_jpeg.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_plus_jpeg.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies JPEG and YUV images are similar."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw.py
index e816a80..60c1ea1 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies RAW and YUV images are similar."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw10.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw10.py
index f1f0d83..4b763c7 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw10.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw10.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies RAW10 and YUV images are similar."""
import logging
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw12.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw12.py
index a80b3e4..4d1b778 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw12.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw12.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies RAW12 and YUV images are similar."""
import logging
diff --git a/apps/CameraITS/tests/scene2_a/test_effects.py b/apps/CameraITS/tests/scene2_a/test_effects.py
index 59c01ef..b437194 100644
--- a/apps/CameraITS/tests/scene2_a/test_effects.py
+++ b/apps/CameraITS/tests/scene2_a/test_effects.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies android.control.availableEffects that are supported."""
import logging
diff --git a/apps/CameraITS/tests/scene2_a/test_faces.py b/apps/CameraITS/tests/scene2_a/test_faces.py
index bd7d3bc..220628d 100644
--- a/apps/CameraITS/tests/scene2_a/test_faces.py
+++ b/apps/CameraITS/tests/scene2_a/test_faces.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies faces are detected and landmarks in bounding boxes."""
import logging
diff --git a/apps/CameraITS/tests/scene2_a/test_format_combos.py b/apps/CameraITS/tests/scene2_a/test_format_combos.py
index d299d09..740de49 100644
--- a/apps/CameraITS/tests/scene2_a/test_format_combos.py
+++ b/apps/CameraITS/tests/scene2_a/test_format_combos.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies different combinations of output formats."""
import logging
diff --git a/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py b/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
index a46dde7..57ba5b2 100644
--- a/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
+++ b/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifiers android.jpeg.quality increases JPEG image quality."""
import logging
diff --git a/apps/CameraITS/tests/scene2_a/test_num_faces.py b/apps/CameraITS/tests/scene2_a/test_num_faces.py
index 5d8eea1..9cdca32 100644
--- a/apps/CameraITS/tests/scene2_a/test_num_faces.py
+++ b/apps/CameraITS/tests/scene2_a/test_num_faces.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies 3 faces with different skin tones are detected."""
import logging
diff --git a/apps/CameraITS/tests/scene2_b/test_auto_per_frame_control.py b/apps/CameraITS/tests/scene2_b/test_auto_per_frame_control.py
index b947d4f..eaa5531 100644
--- a/apps/CameraITS/tests/scene2_b/test_auto_per_frame_control.py
+++ b/apps/CameraITS/tests/scene2_b/test_auto_per_frame_control.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies per_frame_control."""
import logging
diff --git a/apps/CameraITS/tests/scene2_e/test_continuous_picture.py b/apps/CameraITS/tests/scene2_e/test_continuous_picture.py
index 48e49ea..acd914f 100644
--- a/apps/CameraITS/tests/scene2_e/test_continuous_picture.py
+++ b/apps/CameraITS/tests/scene2_e/test_continuous_picture.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies 3A converges in CONTINUOUS_PICTURE mode."""
import logging
diff --git a/apps/CameraITS/tests/scene3/test_3a_consistency.py b/apps/CameraITS/tests/scene3/test_3a_consistency.py
index 0fa008e..71469fe 100644
--- a/apps/CameraITS/tests/scene3/test_3a_consistency.py
+++ b/apps/CameraITS/tests/scene3/test_3a_consistency.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies 3A settles consistently 3x."""
import logging
diff --git a/apps/CameraITS/tests/scene3/test_edge_enhancement.py b/apps/CameraITS/tests/scene3/test_edge_enhancement.py
index 2168ffd..9539971 100644
--- a/apps/CameraITS/tests/scene3/test_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_edge_enhancement.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies android.edge.mode works properly."""
import logging
@@ -21,9 +22,9 @@
import its_base_test
import camera_properties_utils
import capture_request_utils
-import cv2_image_processing_utils
import image_processing_utils
import its_session_utils
+import opencv_processing_utils
EDGE_MODES = {'OFF': 0, 'FAST': 1, 'HQ': 2, 'ZSL': 3}
NAME = os.path.splitext(os.path.basename(__file__))[0]
@@ -108,7 +109,7 @@
cam, props, self.scene, self.tablet, self.chart_distance)
# Initialize chart class and locate chart in scene
- chart = cv2_image_processing_utils.Chart(
+ chart = opencv_processing_utils.Chart(
cam, props, self.log_path, chart_loc=chart_loc_arg)
# Define format
diff --git a/apps/CameraITS/tests/scene3/test_flip_mirror.py b/apps/CameraITS/tests/scene3/test_flip_mirror.py
index 6265123..2282a28 100644
--- a/apps/CameraITS/tests/scene3/test_flip_mirror.py
+++ b/apps/CameraITS/tests/scene3/test_flip_mirror.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies image is not flipped or mirrored."""
import logging
@@ -24,9 +25,9 @@
import its_base_test
import camera_properties_utils
import capture_request_utils
-import cv2_image_processing_utils
import image_processing_utils
import its_session_utils
+import opencv_processing_utils
CHART_FILE = os.path.join(os.environ['CAMERA_ITS_TOP'], 'test_images',
@@ -70,8 +71,8 @@
y = image_processing_utils.rotate_img_per_argv(y)
patch = image_processing_utils.get_image_patch(y, chart.xnorm, chart.ynorm,
chart.wnorm, chart.hnorm)
- patch = 255 * cv2_image_processing_utils.gray_scale_img(patch)
- patch = cv2_image_processing_utils.scale_img(
+ patch = 255 * opencv_processing_utils.gray_scale_img(patch)
+ patch = opencv_processing_utils.scale_img(
patch.astype(np.uint8), chart.scale)
# check image has content
@@ -150,7 +151,7 @@
camera_properties_utils.read_3a(props))
# initialize chart class and locate chart in scene
- chart = cv2_image_processing_utils.Chart(
+ chart = opencv_processing_utils.Chart(
cam, props, self.log_path, chart_loc=chart_loc_arg)
fmt = {'format': 'yuv', 'width': VGA_W, 'height': VGA_H}
diff --git a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
index e67000f..9eba414 100644
--- a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
+++ b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies android.lens.state when lens is moving."""
import logging
@@ -21,9 +22,9 @@
import its_base_test
import camera_properties_utils
import capture_request_utils
-import cv2_image_processing_utils
import image_processing_utils
import its_session_utils
+import opencv_processing_utils
FRAME_ATOL_MS = 10
@@ -117,7 +118,7 @@
cam, props, self.scene, self.tablet, self.chart_distance)
# Initialize chart class and locate chart in scene
- chart = cv2_image_processing_utils.Chart(
+ chart = opencv_processing_utils.Chart(
cam, props, self.log_path, chart_loc=chart_loc_arg)
# Get proper sensitivity, exposure time, and focus distance with 3A.
diff --git a/apps/CameraITS/tests/scene3/test_lens_position.py b/apps/CameraITS/tests/scene3/test_lens_position.py
index 1901d34..09f74f0 100644
--- a/apps/CameraITS/tests/scene3/test_lens_position.py
+++ b/apps/CameraITS/tests/scene3/test_lens_position.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies android.lens.focusDistance for lens moving and stationary."""
import logging
@@ -21,10 +22,10 @@
import its_base_test
import camera_properties_utils
import capture_request_utils
-import cv2_image_processing_utils
import error_util
import image_processing_utils
import its_session_utils
+import opencv_processing_utils
FRAME_ATOL_MS = 10 # ms
LENS_MOVING_STATE = 1
@@ -190,7 +191,7 @@
self.chart_distance)
# Initialize chart class and locate chart in scene
- chart = cv2_image_processing_utils.Chart(
+ chart = opencv_processing_utils.Chart(
cam, props, self.log_path, chart_loc=chart_loc_arg)
# Initialize capture format
diff --git a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
index e616ab2..b19ac1f 100644
--- a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verifies android.edge.mode param behavior for reprocessing reqs."""
import logging
@@ -23,9 +24,9 @@
import its_base_test
import camera_properties_utils
import capture_request_utils
-import cv2_image_processing_utils
import image_processing_utils
import its_session_utils
+import opencv_processing_utils
EDGE_MODES = {'OFF': 0, 'FAST': 1, 'HQ': 2, 'ZSL': 3}
EDGE_MODES_VALUES = list(EDGE_MODES.values())
@@ -37,9 +38,9 @@
def check_edge_modes(sharpness):
"""Check that the sharpness for the different edge modes is correct."""
- logging.debug(' Verify HQ is sharper than OFF')
+ logging.debug('Verify HQ is sharper than OFF')
if sharpness[EDGE_MODES['HQ']] < sharpness[EDGE_MODES['OFF']]:
- raise AssertionError(f"HQ: {sharpness[EDGE_MODES['HQ']]:.5f}, "
+ raise AssertionError(f"HQ < OFF! HQ: {sharpness[EDGE_MODES['HQ']]:.5f}, "
f"OFF: {sharpness[EDGE_MODES['OFF']]:.5f}")
logging.debug('Verify ZSL is similar to OFF')
@@ -115,7 +116,8 @@
edge_mode_res = caps[n]['metadata']['android.edge.mode']
sharpness_list.append(
image_processing_utils.compute_image_sharpness(chart.img))
-
+ logging.debug('Sharpness list for edge mode %d: %s',
+ edge_mode, str(sharpness_list))
return {'edge_mode': edge_mode_res, 'sharpness': np.mean(sharpness_list)}
@@ -155,7 +157,7 @@
cam, props, self.scene, self.tablet, self.chart_distance)
# Initialize chart class and locate chart in scene
- chart = cv2_image_processing_utils.Chart(
+ chart = opencv_processing_utils.Chart(
cam, props, self.log_path, chart_loc=chart_loc_arg)
# If reprocessing is supported, ZSL edge mode must be avaiable.
@@ -169,6 +171,7 @@
reprocess_formats.append('private')
size = capture_request_utils.get_available_output_sizes('jpg', props)[0]
+ logging.debug('image W: %d, H: %d', size[0], size[1])
out_surface = {'width': size[0], 'height': size[1], 'format': 'jpg'}
# Get proper sensitivity, exposure time, and focus distance.
diff --git a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
index b7dc193..842ac73 100644
--- a/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
+++ b/apps/CameraITS/tests/scene4/test_aspect_ratio_and_crop.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Validate aspect ratio, crop and FoV vs format."""
import logging
@@ -23,9 +24,9 @@
import its_base_test
import camera_properties_utils
import capture_request_utils
-import cv2_image_processing_utils
import image_processing_utils
import its_session_utils
+import opencv_processing_utils
_CIRCLE_COLOR = 0 # [0: black, 255: white].
_CIRCLE_MIN_AREA = 0.01 # 1% of image size.
@@ -221,10 +222,10 @@
# Find circle.
img_raw *= 255 # cv2 needs images between [0,255].
- circle_raw = cv2_image_processing_utils.find_circle(
+ circle_raw = opencv_processing_utils.find_circle(
img_raw, img_name, _CIRCLE_MIN_AREA, _CIRCLE_COLOR)
- cv2_image_processing_utils.append_circle_center_to_img(circle_raw, img_raw,
- img_name)
+ opencv_processing_utils.append_circle_center_to_img(circle_raw, img_raw,
+ img_name)
# Determine final return values.
aspect_ratio_gt = circle_raw['w'] / circle_raw['h']
@@ -267,10 +268,10 @@
img *= 255 # cv2 works with [0,255] images.
logging.debug('Captured JPEG %dx%d', w, h)
img_name = '%s_jpeg_w%d_h%d.png' % (os.path.join(log_path, _NAME), w, h)
- circle_jpg = cv2_image_processing_utils.find_circle(
+ circle_jpg = opencv_processing_utils.find_circle(
img, img_name, _CIRCLE_MIN_AREA, _CIRCLE_COLOR)
- cv2_image_processing_utils.append_circle_center_to_img(circle_jpg, img,
- img_name)
+ opencv_processing_utils.append_circle_center_to_img(circle_jpg, img,
+ img_name)
# Determine final return values.
cc_ct_gt = {'hori': circle_jpg['x_offset'], 'vert': circle_jpg['y_offset']}
@@ -528,11 +529,11 @@
img *= 255 # cv2 uses [0, 255].
img_name = '%s_%s_with_%s_w%d_h%d.png' % (
os.path.join(log_path, _NAME), fmt_iter, fmt_cmpr, w_iter, h_iter)
- circle = cv2_image_processing_utils.find_circle(
+ circle = opencv_processing_utils.find_circle(
img, img_name, _CIRCLE_MIN_AREA, _CIRCLE_COLOR)
if debug:
- cv2_image_processing_utils.append_circle_center_to_img(circle, img,
- img_name)
+ opencv_processing_utils.append_circle_center_to_img(circle, img,
+ img_name)
# Check pass/fail for fov coverage for all fmts in AR_CHECKED
img /= 255 # image_processing_utils uses [0, 1].
diff --git a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
index 2619c73..1016687 100644
--- a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
+++ b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verify multi-camera alignment using internal parameters."""
import logging
@@ -23,9 +24,9 @@
import its_base_test
import camera_properties_utils
import capture_request_utils
-import cv2_image_processing_utils
import image_processing_utils
import its_session_utils
+import opencv_processing_utils
ALIGN_TOL_MM = 4.0 # mm
ALIGN_TOL = 0.01 # multiplied by sensor diagonal to convert to pixels
@@ -108,19 +109,19 @@
logging.debug('Camera: %s, FoV: %.2f, chart_distance: %.1fcm', i, fov,
chart_distance)
# determine best combo with rig used or recommend different rig
- if (cv2_image_processing_utils.FOV_THRESH_TELE < fov <
- cv2_image_processing_utils.FOV_THRESH_WFOV):
+ if (opencv_processing_utils.FOV_THRESH_TELE < fov <
+ opencv_processing_utils.FOV_THRESH_WFOV):
test_ids.append(i) # RFoV camera
- elif fov < cv2_image_processing_utils.FOV_THRESH_SUPER_TELE:
+ elif fov < opencv_processing_utils.FOV_THRESH_SUPER_TELE:
logging.debug('Skipping camera. Not appropriate multi-camera testing.')
continue # super-TELE camera
- elif (fov <= cv2_image_processing_utils.FOV_THRESH_TELE and
+ elif (fov <= opencv_processing_utils.FOV_THRESH_TELE and
np.isclose(chart_distance,
- cv2_image_processing_utils.CHART_DISTANCE_RFOV, rtol=0.1)):
+ opencv_processing_utils.CHART_DISTANCE_RFOV, rtol=0.1)):
test_ids.append(i) # TELE camera in RFoV rig
- elif (fov >= cv2_image_processing_utils.FOV_THRESH_WFOV and
+ elif (fov >= opencv_processing_utils.FOV_THRESH_WFOV and
np.isclose(chart_distance,
- cv2_image_processing_utils.CHART_DISTANCE_WFOV, rtol=0.1)):
+ opencv_processing_utils.CHART_DISTANCE_WFOV, rtol=0.1)):
test_ids.append(i) # WFoV camera in WFoV rig
else:
logging.debug('Skipping camera. Not appropriate for test rig.')
@@ -492,7 +493,7 @@
os.path.join(log_path, NAME), fmt, i))
# Find the circles in grayscale image
- circle[i] = cv2_image_processing_utils.find_circle(
+ circle[i] = opencv_processing_utils.find_circle(
img, '%s_%s_gray_%s.jpg' % (os.path.join(log_path, NAME), fmt, i),
CIRCLE_MIN_AREA, CIRCLE_COLOR)
logging.debug('Circle radius %s: %.2f', format(i), circle[i]['r'])
diff --git a/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py b/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py
index 2623b10..e5c431c 100644
--- a/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py
+++ b/apps/CameraITS/tests/scene5/test_lens_shading_and_color_uniformity.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Test lens shading and color uniformity with diffuser over camera."""
import logging
diff --git a/apps/CameraITS/tests/scene6/test_zoom.py b/apps/CameraITS/tests/scene6/test_zoom.py
index 7a07d34..8b32057 100644
--- a/apps/CameraITS/tests/scene6/test_zoom.py
+++ b/apps/CameraITS/tests/scene6/test_zoom.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verify zoom ratio scales circle sizes correctly."""
import logging
@@ -23,9 +24,9 @@
import its_base_test
import camera_properties_utils
import capture_request_utils
-import cv2_image_processing_utils
import image_processing_utils
import its_session_utils
+import opencv_processing_utils
CIRCLE_COLOR = 0 # [0: black, 255: white]
CIRCLE_TOL = 0.05 # contour area vs ideal circle area pi*((w+h)/4)**2
@@ -103,7 +104,7 @@
for contour in contours:
area = cv2.contourArea(contour)
if area > min_area and len(contour) >= MIN_CIRCLE_PTS:
- shape = cv2_image_processing_utils.component_shape(contour)
+ shape = opencv_processing_utils.component_shape(contour)
radius = (shape['width'] + shape['height']) / 4
colour = img_bw[shape['cty']][shape['ctx']]
circlish = round((math.pi * radius**2) / area, 4)
diff --git a/apps/CameraITS/tests/scene_change/test_scene_change.py b/apps/CameraITS/tests/scene_change/test_scene_change.py
index 614501f..9a8b262 100644
--- a/apps/CameraITS/tests/scene_change/test_scene_change.py
+++ b/apps/CameraITS/tests/scene_change/test_scene_change.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verify that the android.control.afSceneChange asserted on scene change."""
import logging
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 b380378..3afd554 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
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verify multiple cameras take images in same time base."""
import logging
@@ -26,9 +27,9 @@
import its_base_test
import camera_properties_utils
import capture_request_utils
-import cv2_image_processing_utils
import image_processing_utils
import its_session_utils
+import opencv_processing_utils
import sensor_fusion_utils
_ANGLE_90_MASK = 10 # (degrees) mask around 0/90 as rotated squares look same
@@ -161,8 +162,8 @@
req['android.lens.focusDistance'] = fd_chart
# Capture YUVs
- width = cv2_image_processing_utils.VGA_WIDTH
- height = cv2_image_processing_utils.VGA_HEIGHT
+ width = opencv_processing_utils.VGA_WIDTH
+ height = opencv_processing_utils.VGA_HEIGHT
out_surfaces = [{'format': 'yuv', 'width': width, 'height': height,
'physicalCamera': ids[0]},
{'format': 'yuv', 'width': width, 'height': height,
@@ -257,8 +258,8 @@
# Compute angles in frame pairs
frame_pairs_angles = [
- [cv2_image_processing_utils.get_angle(p[0]),
- cv2_image_processing_utils.get_angle(p[1])] for p in frame_pairs_gray]
+ [opencv_processing_utils.get_angle(p[0]),
+ opencv_processing_utils.get_angle(p[1])] for p in frame_pairs_gray]
# Remove frames where not enough squares were detected.
filtered_pairs_angles = _remove_frames_without_enough_squares(
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
index f580558..d6dded3 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Verify image and inertial sensor events are well synchronized."""
import json
@@ -273,7 +274,7 @@
# create mask
ymin = int(h * (1 - _FEATURE_MARGIN) / 2)
ymax = int(h * (1 + _FEATURE_MARGIN) / 2)
- mask = numpy.zeros_like(gframes[0])
+ mask = np.zeros_like(gframes[0])
mask[ymin:ymax, :] = 255
for i in range(1, len(gframes)):
@@ -322,7 +323,7 @@
Args:
best: x value of best fit data.
- coeff: 3 element np array. Return of numpy.polyfit(x,y) for 2nd order fit.
+ coeff: 3 element np array. Return of np.polyfit(x,y) for 2nd order fit.
x: np array of x data that was fit.
y: np array of y data that was fit.
log_path: where to store data.
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index bc202fd..837cddb 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -561,18 +561,19 @@
tot_pass += num_pass
logging.info('scene tests: %s, Total tests passed: %s', tot_tests,
tot_pass)
- logging.info('%s compatibility score: %.f/100\n',
- s, 100 * num_pass / tot_tests)
-
- scene_test_summary_path = os.path.join(mobly_scene_output_logs_path,
- 'scene_test_summary.txt')
- with open(scene_test_summary_path, 'w') as f:
- f.write(scene_test_summary)
-
- results[s][RESULT_KEY] = (RESULT_PASS if num_fail == 0 else RESULT_FAIL)
- results[s][SUMMARY_KEY] = scene_test_summary_path
- results[s][TIME_KEY_START] = scene_start_time
- results[s][TIME_KEY_END] = scene_end_time
+ if tot_tests > 0:
+ logging.info('%s compatibility score: %.f/100\n',
+ s, 100 * num_pass / tot_tests)
+ scene_test_summary_path = os.path.join(mobly_scene_output_logs_path,
+ 'scene_test_summary.txt')
+ with open(scene_test_summary_path, 'w') as f:
+ f.write(scene_test_summary)
+ results[s][RESULT_KEY] = (RESULT_PASS if num_fail == 0 else RESULT_FAIL)
+ results[s][SUMMARY_KEY] = scene_test_summary_path
+ results[s][TIME_KEY_START] = scene_start_time
+ results[s][TIME_KEY_END] = scene_end_time
+ else:
+ logging.info('%s compatibility score: 0/100\n')
# Delete temporary yml file after scene run.
new_yaml_file_path = os.path.join(YAML_FILE_DIR, new_yml_file_name)
diff --git a/apps/CameraITS/utils/camera_properties_utils.py b/apps/CameraITS/utils/camera_properties_utils.py
index 6b5e74d..94ab23b 100644
--- a/apps/CameraITS/utils/camera_properties_utils.py
+++ b/apps/CameraITS/utils/camera_properties_utils.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Utility functions to determine what functionality the camera supports."""
import logging
diff --git a/apps/CameraITS/utils/capture_request_utils.py b/apps/CameraITS/utils/capture_request_utils.py
index 4f9afa8..e3825a9 100644
--- a/apps/CameraITS/utils/capture_request_utils.py
+++ b/apps/CameraITS/utils/capture_request_utils.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Utility functions to create custom capture requests."""
import logging
diff --git a/apps/CameraITS/utils/error_util.py b/apps/CameraITS/utils/error_util.py
index 5926670..869cb09 100644
--- a/apps/CameraITS/utils/error_util.py
+++ b/apps/CameraITS/utils/error_util.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Exception raised during ITS test execution."""
class CameraItsError(Exception):
diff --git a/apps/CameraITS/utils/image_processing_utils.py b/apps/CameraITS/utils/image_processing_utils.py
index 0b97111..dd7ea4d 100644
--- a/apps/CameraITS/utils/image_processing_utils.py
+++ b/apps/CameraITS/utils/image_processing_utils.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Image processing utility functions."""
import copy
diff --git a/apps/CameraITS/utils/its_session_utils.py b/apps/CameraITS/utils/its_session_utils.py
index 7b2c6b8..95fb88e 100644
--- a/apps/CameraITS/utils/its_session_utils.py
+++ b/apps/CameraITS/utils/its_session_utils.py
@@ -11,6 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Utility functions to form an ItsSession and perform various camera actions.
+"""
import collections
@@ -29,9 +31,9 @@
import camera_properties_utils
import capture_request_utils
-import cv2_image_processing_utils
import error_util
import image_processing_utils
+import opencv_processing_utils
LOAD_SCENE_DELAY_SEC = 3
SUB_CAMERA_SEPARATOR = '.'
@@ -1004,20 +1006,20 @@
file_name: file name to display on the tablet.
"""
- chart_scaling = cv2_image_processing_utils.calc_chart_scaling(
+ chart_scaling = opencv_processing_utils.calc_chart_scaling(
chart_distance, camera_fov)
if numpy.isclose(
chart_scaling,
- cv2_image_processing_utils.SCALE_TELE_IN_WFOV_BOX,
+ opencv_processing_utils.SCALE_TELE_IN_WFOV_BOX,
atol=0.01):
file_name = '%s_%sx_scaled.pdf' % (
- scene, str(cv2_image_processing_utils.SCALE_TELE_IN_WFOV_BOX))
+ scene, str(opencv_processing_utils.SCALE_TELE_IN_WFOV_BOX))
elif numpy.isclose(
chart_scaling,
- cv2_image_processing_utils.SCALE_RFOV_IN_WFOV_BOX,
+ opencv_processing_utils.SCALE_RFOV_IN_WFOV_BOX,
atol=0.01):
file_name = '%s_%sx_scaled.pdf' % (
- scene, str(cv2_image_processing_utils.SCALE_RFOV_IN_WFOV_BOX))
+ scene, str(opencv_processing_utils.SCALE_RFOV_IN_WFOV_BOX))
else:
file_name = '%s.pdf' % scene
logging.debug('Scene to load: %s', file_name)
@@ -1054,6 +1056,25 @@
return data['strValue'] == 'supportedCombination'
+ def is_camera_privacy_mode_supported(self):
+ """Query whether the mobile device supports camera privacy mode.
+
+ This function checks whether the mobile device has FEATURE_CAMERA_TOGGLE
+ feature support, which indicates the camera device can run in privacy mode.
+
+ Returns:
+ Boolean
+ """
+ cmd = {}
+ cmd['cmdName'] = 'isCameraPrivacyModeSupported'
+ self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+
+ data, _ = self.__read_response_from_socket()
+ if data['tag'] != 'cameraPrivacyModeSupport':
+ raise error_util.CameraItsError('Failed to query camera privacy mode'
+ ' support')
+ return data['strValue'] == 'true'
+
def parse_camera_ids(ids):
"""Parse the string of camera IDs into array of CameraIdCombo tuples.
@@ -1128,14 +1149,14 @@
rfov_camera_in_rfov_box = (
numpy.isclose(
chart_distance,
- cv2_image_processing_utils.CHART_DISTANCE_RFOV, rtol=0.1) and
- cv2_image_processing_utils.FOV_THRESH_TELE <= float(camera_fov)
- <= cv2_image_processing_utils.FOV_THRESH_WFOV)
+ opencv_processing_utils.CHART_DISTANCE_RFOV, rtol=0.1) and
+ opencv_processing_utils.FOV_THRESH_TELE <= float(camera_fov)
+ <= opencv_processing_utils.FOV_THRESH_WFOV)
wfov_camera_in_wfov_box = (
numpy.isclose(
chart_distance,
- cv2_image_processing_utils.CHART_DISTANCE_WFOV, rtol=0.1) and
- float(camera_fov) > cv2_image_processing_utils.FOV_THRESH_WFOV)
+ opencv_processing_utils.CHART_DISTANCE_WFOV, rtol=0.1) and
+ float(camera_fov) > opencv_processing_utils.FOV_THRESH_WFOV)
if rfov_camera_in_rfov_box or wfov_camera_in_wfov_box:
cam.do_3a()
cap = cam.do_capture(
diff --git a/apps/CameraITS/utils/cv2_image_processing_utils.py b/apps/CameraITS/utils/opencv_processing_utils.py
similarity index 99%
rename from apps/CameraITS/utils/cv2_image_processing_utils.py
rename to apps/CameraITS/utils/opencv_processing_utils.py
index 137e0a3..ac0daa0 100644
--- a/apps/CameraITS/utils/cv2_image_processing_utils.py
+++ b/apps/CameraITS/utils/opencv_processing_utils.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Image processing utilities using openCV."""
import logging
@@ -30,8 +31,8 @@
ANGLE_NUM_MIN = 10 # Minimum number of angles for find_angle() to be valid
-CHART_FILE = os.path.join(os.environ['CAMERA_ITS_TOP'], 'test_images',
- 'ISO12233.png')
+TEST_IMG_DIR = os.path.join(os.environ['CAMERA_ITS_TOP'], 'test_images')
+CHART_FILE = os.path.join(TEST_IMG_DIR, 'ISO12233.png')
CHART_HEIGHT = 13.5 # cm
CHART_DISTANCE_RFOV = 31.0 # cm
CHART_DISTANCE_WFOV = 22.0 # cm
@@ -63,8 +64,6 @@
SQUARE_AREA_MIN_REL = 0.05 # Minimum size for square relative to image area
SQUARE_TOL = 0.1 # Square W vs H mismatch RTOL
-TEST_IMG_DIR = os.path.join(os.environ['CAMERA_ITS_TOP'], 'test_images')
-
VGA_HEIGHT = 480
VGA_WIDTH = 640
diff --git a/apps/CameraITS/utils/scene_change_utils.py b/apps/CameraITS/utils/scene_change_utils.py
index 0694ddf..fca397b 100644
--- a/apps/CameraITS/utils/scene_change_utils.py
+++ b/apps/CameraITS/utils/scene_change_utils.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Utility functions for scene change test."""
import logging
diff --git a/apps/CameraITS/utils/sensor_fusion_utils.py b/apps/CameraITS/utils/sensor_fusion_utils.py
index 2935d5b..6ce53b1 100644
--- a/apps/CameraITS/utils/sensor_fusion_utils.py
+++ b/apps/CameraITS/utils/sensor_fusion_utils.py
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Utility functions for sensor_fusion hardware rig."""
import bisect
diff --git a/apps/CameraITS/utils/target_exposure_utils.py b/apps/CameraITS/utils/target_exposure_utils.py
index 04c5f2e..9b6c422 100644
--- a/apps/CameraITS/utils/target_exposure_utils.py
+++ b/apps/CameraITS/utils/target_exposure_utils.py
@@ -11,7 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
+"""Utility functions to calculate targeted exposures based on camera properties.
+"""
import json
import logging
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 57dc2de..990cc22 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -32,6 +32,8 @@
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index cac1dc5..51b40ce 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2396,6 +2396,12 @@
<string name="request_manage_credentials">Request to manage credentials</string>
<string name="is_credential_management_app">Check is credential management app</string>
<string name="credential_management_app_policy">Check correct authentication policy is set</string>
+ <string name="generate_key_pair">Generate key pair</string>
+ <string name="create_and_install_certificate">Create and install certificate</string>
+ <string name="request_certificate_authentication">Request certificate for authentication</string>
+ <string name="sign_data_with_key">Sign data with the private key</string>
+ <string name="verify_signature">Verify signature with the public key</string>
+ <string name="remove_credential_management_app">Remove credential management app</string>
<!-- Strings for Widget -->
<string name="widget_framework_test">Widget Framework Test</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
index 48f4c6b..75a9adc 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
@@ -407,7 +407,7 @@
.loadClass("android.os.SystemProperties")
.getMethod("get", String.class);
String emulatorKernel = (String) getStringMethod.invoke("0",
- "ro.kernel.qemu");
+ "ro.boot.qemu");
if (emulatorKernel.equals("1")) {
return false;
}
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 7ce97fe..8792ece 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
@@ -22,6 +22,7 @@
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.graphics.ImageFormat;
import android.graphics.Rect;
@@ -231,6 +232,8 @@
private HandlerThread mSensorThread = null;
private Handler mSensorHandler = null;
+ private PackageManager mPackageManager;
+
private static final int SERIALIZER_SURFACES_ID = 2;
private static final int SERIALIZER_PHYSICAL_METADATA_ID = 3;
@@ -312,6 +315,8 @@
mChannel.setDescription("ItsServiceChannel");
mChannel.enableVibration(false);
notificationManager.createNotificationChannel(mChannel);
+
+ mPackageManager = getPackageManager();
}
@Override
@@ -709,6 +714,8 @@
mSocketRunnableObj.sendResponse("ItsVersion", ITS_SERVICE_VERSION);
} else if ("isStreamCombinationSupported".equals(cmdObj.getString("cmdName"))) {
doCheckStreamCombination(cmdObj);
+ } else if ("isCameraPrivacyModeSupported".equals(cmdObj.getString("cmdName"))) {
+ doCheckCameraPrivacyModeSupport();
} else {
throw new ItsException("Unknown command: " + cmd);
}
@@ -1038,6 +1045,13 @@
}
}
+ private void doCheckCameraPrivacyModeSupport() throws ItsException {
+ boolean hasPrivacySupport = mPackageManager.hasSystemFeature(
+ PackageManager.FEATURE_CAMERA_TOGGLE);
+ mSocketRunnableObj.sendResponse("cameraPrivacyModeSupport",
+ hasPrivacySupport ? "true" : "false");
+ }
+
private void prepareImageReaders(Size[] outputSizes, int[] outputFormats, Size inputSize,
int inputFormat, int maxInputBuffers) {
closeImageReaders();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/CredentialManagementAppActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/CredentialManagementAppActivity.java
index 6cb48bc..2d58d17 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/CredentialManagementAppActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/CredentialManagementAppActivity.java
@@ -16,22 +16,42 @@
package com.android.cts.verifier.security;
+import static android.keystore.cts.CertificateUtils.createCertificate;
+
+import android.app.admin.DevicePolicyManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.security.AppUriAuthenticationPolicy;
+import android.security.AttestedKeyPair;
import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
+import android.security.KeyChainException;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.util.Log;
import com.android.cts.verifier.ArrayTestListAdapter;
import com.android.cts.verifier.DialogTestListActivity;
import com.android.cts.verifier.R;
import com.android.cts.verifier.TestResult;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import javax.security.auth.x500.X500Principal;
+
/**
* CTS verifier test for credential management on unmanaged device.
*
@@ -40,6 +60,12 @@
* Can successfully request to become the Credential management app
* The credential management app is correctly set
* The authentication policy is correctly set
+ * The credential management app can generate a key pair
+ * The credential management app can install a certificate
+ * The credential management app can successfully predefine which alias should be used for
+ * authentication to a remote service
+ * The chosen alias can be used to get the private key to sign data and the public key to
+ * validate the signature.
*/
public class CredentialManagementAppActivity extends DialogTestListActivity {
@@ -47,21 +73,38 @@
private static final int REQUEST_MANAGE_CREDENTIALS_STATUS = 0;
- private static final String TEST_APP_PACKAGE_NAME = "com.test.pkg";
+ private static final String TEST_APP_PACKAGE_NAME = "com.android.cts.verifier";
private static final Uri TEST_URI = Uri.parse("https://test.com");
private static final String TEST_ALIAS = "testAlias";
private static final AppUriAuthenticationPolicy AUTHENTICATION_POLICY =
new AppUriAuthenticationPolicy.Builder()
.addAppAndUriMapping(TEST_APP_PACKAGE_NAME, TEST_URI, TEST_ALIAS)
.build();
+ private static final String KEY_ALGORITHM = "RSA";
+ private static final byte[] DATA = "test".getBytes();
+
+ private DevicePolicyManager mDevicePolicyManager;
private DialogTestListItem mRequestManageCredentials;
private DialogTestListItem mCheckIsCredentialManagementApp;
private DialogTestListItem mCheckAuthenticationPolicy;
+ private DialogTestListItem mGenerateKeyPair;
+ private DialogTestListItem mCreateAndInstallCertificate;
+ private DialogTestListItem mRequestCertificateForAuthentication;
+ private DialogTestListItem mSignDataWithKey;
+ private DialogTestListItem mVerifySignature;
+ private DialogTestListItem mRemoveCredentialManagementApp;
+
+ private AttestedKeyPair mAttestedKeyPair;
+ private X509Certificate mCertificate;
+ private String mChosenAlias;
+ private byte[] mSignature;
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private boolean mHasCredentialManagementApp = false;
+
public CredentialManagementAppActivity() {
super(R.layout.credential_management_app_test,
R.string.credential_management_app_test,
@@ -72,6 +115,16 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ mDevicePolicyManager = getSystemService(DevicePolicyManager.class);
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ if (mHasCredentialManagementApp) {
+ mExecutor.execute(
+ () -> KeyChain.removeCredentialManagementApp(getApplicationContext()));
+ }
}
@Override
@@ -104,6 +157,60 @@
}
};
testAdapter.add(mCheckAuthenticationPolicy);
+ mGenerateKeyPair = new DialogTestListItem(this,
+ R.string.generate_key_pair,
+ "generate_key_pair") {
+ @Override
+ public void performTest(DialogTestListActivity activity) {
+ generateKeyPair();
+ }
+ };
+ testAdapter.add(mGenerateKeyPair);
+ mCreateAndInstallCertificate = new DialogTestListItem(this,
+ R.string.create_and_install_certificate,
+ "create_and_install_certificate") {
+ @Override
+ public void performTest(DialogTestListActivity activity) {
+ createAndInstallCertificate();
+ }
+ };
+ testAdapter.add(mCreateAndInstallCertificate);
+ mRequestCertificateForAuthentication = new DialogTestListItem(this,
+ R.string.request_certificate_authentication,
+ "request_certificate_authentication") {
+ @Override
+ public void performTest(DialogTestListActivity activity) {
+ requestCertificateForAuthentication();
+ }
+ };
+ testAdapter.add(mRequestCertificateForAuthentication);
+ mSignDataWithKey = new DialogTestListItem(this,
+ R.string.sign_data_with_key,
+ "sign_data_with_key") {
+ @Override
+ public void performTest(DialogTestListActivity activity) {
+ getPrivateKeyAndSignData();
+ }
+ };
+ testAdapter.add(mSignDataWithKey);
+ mVerifySignature = new DialogTestListItem(this,
+ R.string.verify_signature,
+ "verify_signature") {
+ @Override
+ public void performTest(DialogTestListActivity activity) {
+ getPublicKeyAndVerifySignature();
+ }
+ };
+ testAdapter.add(mVerifySignature);
+ mRemoveCredentialManagementApp = new DialogTestListItem(this,
+ R.string.remove_credential_management_app,
+ "remove_credential_management_app") {
+ @Override
+ public void performTest(DialogTestListActivity activity) {
+ removeCredentialManagementApp();
+ }
+ };
+ testAdapter.add(mRemoveCredentialManagementApp);
}
private void checkIsCredentialManagementApp() {
@@ -123,6 +230,110 @@
});
}
+ private void generateKeyPair() {
+ mExecutor.execute(() -> {
+ KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(
+ TEST_ALIAS, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setKeySize(2048)
+ .setDigests(KeyProperties.DIGEST_SHA256)
+ .setSignaturePaddings(
+ KeyProperties.SIGNATURE_PADDING_RSA_PSS,
+ KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
+ .build();
+ mAttestedKeyPair = mDevicePolicyManager.generateKeyPair(null, KEY_ALGORITHM,
+ keyGenParameterSpec, 0);
+ mHandler.post(() -> setResult(mGenerateKeyPair, mAttestedKeyPair != null));
+ });
+ }
+
+ private void createAndInstallCertificate() {
+ X500Principal issuer = new X500Principal("CN=SelfSigned, O=Android, C=US");
+ X500Principal subject = new X500Principal("CN=Subject, O=Android, C=US");
+ try {
+ mCertificate = createCertificate(mAttestedKeyPair.getKeyPair(), subject, issuer);
+ setResult(mCreateAndInstallCertificate,
+ mDevicePolicyManager.setKeyPairCertificate(null, TEST_ALIAS,
+ Arrays.asList(new X509Certificate[]{mCertificate}), false));
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to create certificate", e);
+ setResult(mCreateAndInstallCertificate, false);
+ }
+ }
+
+ private void requestCertificateForAuthentication() {
+ String[] keyTypes = new String[]{KEY_ALGORITHM};
+ Principal[] issuers = new Principal[0];
+ TestKeyChainAliasCallback callback = new TestKeyChainAliasCallback();
+ KeyChain.choosePrivateKeyAlias(this, callback, keyTypes, issuers, TEST_URI, null);
+ }
+
+ private void getPrivateKeyAndSignData() {
+ // Get private key with chosen alias
+ mExecutor.execute(() -> {
+ try {
+ final PrivateKey privateKey = KeyChain.getPrivateKey(
+ getApplicationContext(), mChosenAlias);
+ mHandler.post(() -> {
+ // Sign data with the private key
+ try {
+ Signature sign = Signature.getInstance("SHA256withRSA");
+ sign.initSign(privateKey);
+ sign.update(DATA);
+ mSignature = sign.sign();
+ } catch (NoSuchAlgorithmException | InvalidKeyException
+ | SignatureException e) {
+ Log.w(TAG, "Failed to sign data with key", e);
+ }
+ setResult(mSignDataWithKey, mSignature != null);
+ });
+ } catch (KeyChainException | InterruptedException e) {
+ Log.w(TAG, "Failed to get the private key", e);
+ }
+ });
+ }
+
+ private void getPublicKeyAndVerifySignature() {
+ // Get public key from certificate with chosen alias
+ mExecutor.execute(() -> {
+ try {
+ X509Certificate[] certChain =
+ KeyChain.getCertificateChain(getApplicationContext(), mChosenAlias);
+ mHandler.post(() -> {
+ boolean verified = false;
+ if (certChain != null && certChain.length > 0) {
+ PublicKey publicKey = certChain[0].getPublicKey();
+ // Verify the signature with the public key
+ try {
+ Signature verify = Signature.getInstance("SHA256withRSA");
+ verify.initVerify(publicKey);
+ verify.update(DATA);
+ verified = verify.verify(mSignature);
+ } catch (NoSuchAlgorithmException | InvalidKeyException
+ | SignatureException e) {
+ Log.w(TAG, "Failed to verify signature", e);
+ }
+ }
+ setResult(mVerifySignature, verified);
+ });
+ } catch (KeyChainException | InterruptedException e) {
+ Log.w(TAG, "Failed to get the public key", e);
+ }
+ });
+ }
+
+ private void removeCredentialManagementApp() {
+ mExecutor.execute(() -> {
+ final boolean result =
+ KeyChain.removeCredentialManagementApp(getApplicationContext());
+ mHandler.post(() -> {
+ setResult(mRemoveCredentialManagementApp, result);
+ if (result) {
+ mHasCredentialManagementApp = false;
+ }
+ });
+ });
+ }
+
private void setResult(DialogTestListItem testListItem, boolean passed) {
if (passed) {
setTestResult(testListItem, TestResult.TEST_RESULT_PASSED);
@@ -136,9 +347,20 @@
switch (requestCode) {
case REQUEST_MANAGE_CREDENTIALS_STATUS:
setResult(mRequestManageCredentials, resultCode == RESULT_OK);
+ if (resultCode == RESULT_OK) {
+ mHasCredentialManagementApp = true;
+ }
break;
default:
super.handleActivityResult(requestCode, resultCode, data);
}
}
+
+ private class TestKeyChainAliasCallback implements KeyChainAliasCallback {
+ @Override
+ public void alias(String alias) {
+ mChosenAlias = alias;
+ setResult(mRequestCertificateForAuthentication, mChosenAlias.equals(TEST_ALIAS));
+ }
+ }
}
diff --git a/build/test_executable.mk b/build/test_executable.mk
index 1ff9a51..8dca32c 100644
--- a/build/test_executable.mk
+++ b/build/test_executable.mk
@@ -18,4 +18,11 @@
# Replace "include $(BUILD_EXECUTABLE)" with "include $(BUILD_CTS_EXECUTABLE)"
#
+# Implicitly run this test under MTE SYNC for aarch64 binaries. This is a no-op
+# on non-MTE hardware.
+my_arch := $(TARGET_$(LOCAL_2ND_ARCH_VAR_PREFIX)ARCH)
+ifneq (,$(filter arm64,$(my_arch)))
+ LOCAL_WHOLE_STATIC_LIBRARIES += note_memtag_heap_sync
+endif
+
include $(BUILD_EXECUTABLE)
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java
index ac907be..b73cb35 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DeviceOwnerHelper.java
@@ -65,7 +65,7 @@
public static boolean runManagerMethod(DeviceAdminReceiver receiver, Context context,
Intent intent) {
String action = intent.getAction();
- Log.d(TAG, "onReceive(): user=" + context.getUserId() + ", action=" + action);
+ Log.d(TAG, "runManagerMethod(): user=" + context.getUserId() + ", action=" + action);
if (!action.equals(ACTION_WRAPPED_MANAGER_CALL)) return false;
@@ -73,7 +73,7 @@
String className = intent.getStringExtra(EXTRA_CLASS);
String methodName = intent.getStringExtra(EXTRA_METHOD);
int numberArgs = intent.getIntExtra(EXTRA_NUMBER_ARGS, 0);
- Log.d(TAG, "onReceive(): userId=" + context.getUserId()
+ Log.d(TAG, "runManagerMethod(): userId=" + context.getUserId()
+ ", intent=" + intent.getAction() + ", class=" + className
+ ", methodName=" + methodName + ", numberArgs=" + numberArgs);
Object[] args = null;
@@ -85,7 +85,7 @@
for (int i = 0; i < numberArgs; i++) {
getArg(extras, args, parameterTypes, i);
}
- Log.d(TAG, "onReceive(): args=" + Arrays.toString(args) + ", types="
+ Log.d(TAG, "runManagerMethod(): args=" + Arrays.toString(args) + ", types="
+ Arrays.toString(parameterTypes));
}
@@ -101,7 +101,13 @@
: context.getSystemService(managerClass);
Object result = method.invoke(manager, args);
- Log.d(TAG, "onReceive(): result=" + result);
+
+ if (VERBOSE) {
+ // Some results - like network logging events - are quite large
+ Log.v(TAG, "runManagerMethod(): method returned " + result);
+ } else {
+ Log.v(TAG, "runManagerMethod(): method returned fine");
+ }
sendResult(receiver, result);
} catch (Exception e) {
sendError(receiver, e);
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java
index 6abf302..da9b1e0 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/DevicePolicyManagerWrapper.java
@@ -43,11 +43,11 @@
int userId = context.getUserId();
DevicePolicyManager spy = sSpies.get(context);
if (spy != null) {
- Log.d(TAG, "get(): returning cached spy for user " + userId);
+ Log.d(TAG, "getWrapper(): returning cached spy for user " + userId);
return spy;
}
- Log.d(TAG, "get(): creating spy for user " + context.getUserId());
+ Log.d(TAG, "getWrapper(): creating spy for user " + context.getUserId());
spy = Mockito.spy(dpm);
// TODO(b/176993670): ideally there should be a way to automatically mock all DPM methods,
@@ -148,6 +148,9 @@
doAnswer(answer).when(spy).getEndUserSessionMessage(any());
doAnswer(answer).when(spy).setEndUserSessionMessage(any(), any());
+ // Used by SuspendPackageTest
+ doAnswer(answer).when(spy).getPolicyExemptApps();
+
// TODO(b/176993670): add more methods below as tests are converted
} catch (Exception e) {
// Should never happen, but needs to be catch as some methods declare checked exceptions
@@ -155,8 +158,8 @@
}
sSpies.put(context, spy);
- Log.d(TAG, "get(): returning new spy for context " + context + " and user "
- + userId);
+ Log.d(TAG, "getWrapper(): returning new spy for context " + context + " ("
+ + context.getPackageName() + ")" + " and user " + userId);
return spy;
}
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
index 26d59bf..0e6846d 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
@@ -178,10 +178,15 @@
if (!latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
fail("Ordered broadcast for %s() not received in %dms", methodName, TIMEOUT_MS);
}
- if (VERBOSE) Log.d(TAG, "Got response");
Result result = resultRef.get();
- Log.d(TAG, "Received result on user " + userId + ": " + result);
+
+ Log.d(TAG, "Got result on user " + userId + ": " + resultCodeToString(result.code));
+
+ if (VERBOSE) {
+ // Some results - like network logging events - are quite large
+ Log.v(TAG, "Result: " + result);
+ }
switch (result.code) {
case RESULT_OK:
@@ -190,7 +195,7 @@
Exception e = (Exception) result.value;
throw (e instanceof InvocationTargetException) ? e.getCause() : e;
default:
- fail("Received invalid result: %s", result);
+ fail("Received invalid result for method %s: %s", methodName, result);
return null;
}
};
diff --git a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java
index 5099c9e..59c86af 100644
--- a/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java
+++ b/common/device-side/bedstead/eventlib/src/main/java/com/android/eventlib/RemoteEventQuerier.java
@@ -16,6 +16,7 @@
package com.android.eventlib;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.content.Context.BIND_AUTO_CREATE;
import static com.android.eventlib.QueryService.EARLIEST_LOG_TIME_KEY;
@@ -34,7 +35,8 @@
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.compatibility.common.util.SystemUtil;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.permissions.PermissionContext;
import java.time.Duration;
import java.time.Instant;
@@ -54,6 +56,7 @@
private static final String LOG_TAG = "RemoteEventQuerier";
private static final Context sContext =
InstrumentationRegistry.getInstrumentation().getContext();
+ private static final TestApis sTestApis = new TestApis();
private final String mPackageName;
private final EventLogsQuery<E, F> mEventLogsQuery;
@@ -144,7 +147,31 @@
private AtomicReference<IQueryService> mQuery = new AtomicReference<>();
private CountDownLatch mConnectionCountdown;
+ private static final int MAX_INITIALISATION_ATTEMPTS = 10;
+ private static final long INITIALISATION_ATTEMPT_DELAY_MS = 100;
+
private void ensureInitialised() {
+ // We have retries for binding because there are a number of reasons binding could fail in
+ // unpredictable ways
+ int attempts = 0;
+ while (attempts++ < MAX_INITIALISATION_ATTEMPTS) {
+ try {
+ ensureInitialisedOrThrow();
+ return;
+ } catch (Exception e) {
+ // Ignore, we will retry
+ }
+ try {
+ Thread.sleep(INITIALISATION_ATTEMPT_DELAY_MS);
+ } catch (InterruptedException e) {
+ throw new AssertionError("Interrupted while initialising", e);
+ }
+ }
+
+ ensureInitialisedOrThrow();
+ }
+
+ private void ensureInitialisedOrThrow() {
if (mQuery.get() != null) {
return;
}
@@ -168,11 +195,12 @@
AtomicBoolean didBind = new AtomicBoolean(false);
if (mEventLogsQuery.getUserHandle() != null) {
- SystemUtil.runWithShellPermissionIdentity(() -> {
+ try (PermissionContext p =
+ sTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
didBind.set(sContext.bindServiceAsUser(
intent, connection, /* flags= */ BIND_AUTO_CREATE,
mEventLogsQuery.getUserHandle()));
- });
+ }
} else {
didBind.set(sContext.bindService(intent, connection, /* flags= */ BIND_AUTO_CREATE));
}
@@ -183,6 +211,9 @@
} catch (InterruptedException e) {
throw new AssertionError("Interrupted while binding to service", e);
}
+ } else {
+ throw new AssertionError("Tried to bind but call returned false (intent is "
+ + intent + ", user is " + mEventLogsQuery.getUserHandle() + ")");
}
if (mQuery.get() == null) {
diff --git a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java
index ee9c407..4f59572 100644
--- a/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java
+++ b/common/device-side/bedstead/eventlib/src/test/java/com/android/eventlib/EventLogsTest.java
@@ -1200,8 +1200,6 @@
intent.putExtra("DATA", data);
SystemUtil.runWithShellPermissionIdentity(() -> {
- InstrumentationRegistry.getInstrumentation()
- .getUiAutomation().adoptShellPermissionIdentity();
sContext.startActivityAsUser(intent, user.userHandle());
});
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java
index 51c8db8..42f36b8 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java
@@ -16,8 +16,10 @@
package com.android.bedstead.nene;
+import com.android.bedstead.nene.context.Context;
import com.android.bedstead.nene.devicepolicy.DevicePolicy;
import com.android.bedstead.nene.packages.Packages;
+import com.android.bedstead.nene.permissions.Permissions;
import com.android.bedstead.nene.users.Users;
/**
@@ -27,6 +29,7 @@
private final Users mUsers = new Users();
private final Packages mPackages = new Packages(this);
private final DevicePolicy mDevicePolicy = new DevicePolicy(this);
+ private final Context mContext = new Context(this);
/** Access Test APIs related to Users. */
public Users users() {
@@ -42,4 +45,14 @@
public DevicePolicy devicePolicy() {
return mDevicePolicy;
}
+
+ /** Access Test APIs related to permissions. */
+ public Permissions permissions() {
+ return Permissions.sInstance;
+ }
+
+ /** Access Test APIs related to context. */
+ public Context context() {
+ return mContext;
+ }
}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/context/Context.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/context/Context.java
new file mode 100644
index 0000000..aad6392
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/context/Context.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.context;
+
+import android.content.pm.PackageManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.users.UserReference;
+
+/** Test APIs related to Context. */
+public class Context {
+
+ private static final String ANDROID_PACKAGE = "android";
+
+ private static final android.content.Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+ private final TestApis mTestApis;
+
+ public Context(TestApis testApis) {
+ mTestApis = testApis;
+ }
+
+ /**
+ * Get the {@link android.content.Context} for the instrumented test app.
+ */
+ public android.content.Context instrumentedContext() {
+ return sContext;
+ }
+
+ /**
+ * Get the {@link android.content.Context} for the instrumented test app in another user.
+ */
+ public android.content.Context instrumentedContextAsUser(UserReference user) {
+ return sContext.createContextAsUser(user.userHandle(), /* flags= */ 0);
+ }
+
+ /**
+ * Get the {@link android.content.Context} for the "android" package in the given user.
+ */
+ public android.content.Context androidContextAsUser(UserReference user) {
+ try {
+ return sContext.createPackageContextAsUser(
+ ANDROID_PACKAGE, /* flags= */ 0, user.userHandle());
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new NeneException("Could not find android installed in user " + user, e);
+ }
+ }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java
index fb924a1..0c72c9b 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java
@@ -16,19 +16,27 @@
package com.android.bedstead.nene.devicepolicy;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.os.Build.VERSION.SDK_INT;
import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.exceptions.AdbException;
import com.android.bedstead.nene.exceptions.AdbParseException;
import com.android.bedstead.nene.exceptions.NeneException;
import com.android.bedstead.nene.packages.PackageReference;
+import com.android.bedstead.nene.permissions.PermissionContext;
+import com.android.bedstead.nene.users.User;
import com.android.bedstead.nene.users.UserReference;
import com.android.bedstead.nene.utils.ShellCommand;
import com.android.bedstead.nene.utils.ShellCommandUtils;
+import java.util.Collection;
+import java.util.List;
import java.util.Map;
@@ -90,6 +98,15 @@
+ " as the package " + pkg + " is not installed");
}
+ PackageManager p = mTestApis.context().instrumentedContext().getPackageManager();
+ Intent intent = new Intent("android.app.action.DEVICE_ADMIN_ENABLED");
+ intent.setComponent(profileOwnerComponent);
+
+ if (!componentCanBeSetAsDeviceAdmin(profileOwnerComponent, user)) {
+ throw new NeneException("Could not set profile owner for user "
+ + user + " as component " + profileOwnerComponent + " is not valid");
+ }
+
try {
command.executeUntilValid();
} catch (AdbException | InterruptedException e2) {
@@ -122,6 +139,8 @@
throw new NullPointerException();
}
+ // TODO: use setDeviceOwner on S+
+
ShellCommand.Builder command = ShellCommand.builderForUser(
user, "dpm set-device-owner")
.addOperand(deviceOwnerComponent.flattenToShortString())
@@ -152,6 +171,20 @@
+ " as the package " + pkg + " is not installed");
}
+ if (!componentCanBeSetAsDeviceAdmin(deviceOwnerComponent, user)) {
+ throw new NeneException("Could not set device owner for user "
+ + user + " as component " + deviceOwnerComponent + " is not valid");
+ }
+
+ Collection<User> users = mTestApis.users().all();
+
+ if (users.size() > 1) {
+ throw new NeneException("Could not set device owner for user "
+ + user + " as there are already additional users on the device: " + users);
+ }
+
+ // TODO(scottjonathan): Check accounts
+
try {
command.executeUntilValid();
} catch (AdbException | InterruptedException e2) {
@@ -165,6 +198,21 @@
deviceOwnerComponent.getPackageName()), deviceOwnerComponent);
}
+ private boolean componentCanBeSetAsDeviceAdmin(ComponentName component, UserReference user) {
+ PackageManager packageManager =
+ mTestApis.context().instrumentedContext().getPackageManager();
+ Intent intent = new Intent("android.app.action.DEVICE_ADMIN_ENABLED");
+ intent.setComponent(component);
+
+ try (PermissionContext p =
+ mTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+ List<ResolveInfo> r =
+ packageManager.queryBroadcastReceiversAsUser(
+ intent, /* flags= */ 0, user.userHandle());
+ return (!r.isEmpty());
+ }
+ }
+
/**
* Get the device owner.
*/
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/KeepUninstalledPackagesBuilder.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/KeepUninstalledPackagesBuilder.java
index f16a5c9..7069aaa 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/KeepUninstalledPackagesBuilder.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/KeepUninstalledPackagesBuilder.java
@@ -16,15 +16,16 @@
package com.android.bedstead.nene.packages;
-import android.content.Context;
+import static android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES;
+
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.CheckResult;
import androidx.annotation.RequiresApi;
-import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.compatibility.common.util.SystemUtil;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.permissions.PermissionContext;
import java.util.ArrayList;
import java.util.Collection;
@@ -39,11 +40,11 @@
@RequiresApi(Build.VERSION_CODES.S)
public final class KeepUninstalledPackagesBuilder {
- private final List<String> mPackagesToKeep = new ArrayList<>();
- private final Packages mPackages;
+ private final List<String> mPackages = new ArrayList<>();
+ private final TestApis mTestApis;
- KeepUninstalledPackagesBuilder(Packages packages) {
- mPackages = packages;
+ KeepUninstalledPackagesBuilder(TestApis testApis) {
+ mTestApis = testApis;
}
/**
@@ -56,15 +57,13 @@
// TODO(scottjonathan): Investigate if we can make this backwards compatible by pulling the
// APK files and keeping them (either as a file or in memory) until needed to resolve or
// re-install
- Context context = InstrumentationRegistry.getInstrumentation().getContext();
- PackageManager packageManager = context.getPackageManager();
+ PackageManager packageManager =
+ mTestApis.context().instrumentedContext().getPackageManager();
- // TODO(scottjonathan): Once we support permissions in Nene, use that call here
-
- SystemUtil.runWithShellPermissionIdentity(() -> {
- // Needs KEEP_UNINSTALLED_PACKAGES
- packageManager.setKeepUninstalledPackages(mPackagesToKeep);
- });
+ try (PermissionContext p =
+ mTestApis.permissions().withPermission(KEEP_UNINSTALLED_PACKAGES)) {
+ packageManager.setKeepUninstalledPackages(mPackages);
+ }
}
/**
@@ -74,7 +73,7 @@
* any user.
*/
public void clear() {
- mPackagesToKeep.clear();
+ mPackages.clear();
commit();
}
@@ -83,7 +82,7 @@
*/
@CheckResult
public KeepUninstalledPackagesBuilder add(PackageReference pkg) {
- mPackagesToKeep.add(pkg.packageName());
+ mPackages.add(pkg.packageName());
return this;
}
@@ -92,7 +91,7 @@
*/
@CheckResult
public KeepUninstalledPackagesBuilder add(String pkg) {
- return add(mPackages.find(pkg));
+ return add(mTestApis.packages().find(pkg));
}
/**
@@ -111,6 +110,7 @@
*/
@CheckResult
public KeepUninstalledPackagesBuilder addPackageNames(Collection<String> packages) {
- return add(packages.stream().map(mPackages::find).collect(Collectors.toSet()));
+ return add(packages.stream().map(
+ (s) -> mTestApis.packages().find(s)).collect(Collectors.toSet()));
}
}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
index ceef26d..6d1c1f2 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
@@ -177,7 +177,7 @@
public KeepUninstalledPackagesBuilder keepUninstalledPackages() {
Versions.requireS();
- return new KeepUninstalledPackagesBuilder(this);
+ return new KeepUninstalledPackagesBuilder(mTestApis);
}
@Nullable
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/PermissionContext.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/PermissionContext.java
new file mode 100644
index 0000000..868068e
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/PermissionContext.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.permissions;
+
+/**
+ * Collection of required permissions to be granted or denied.
+ *
+ * <p>Once the permissions are no longer required, {@link #close()} should be called.
+ *
+ * <p>It is recommended that this be used as part of a try-with-resource block
+ */
+public interface PermissionContext extends AutoCloseable {
+ @Override
+ void close();
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/PermissionContextImpl.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/PermissionContextImpl.java
new file mode 100644
index 0000000..0273f80
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/PermissionContextImpl.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.permissions;
+
+import com.android.bedstead.nene.exceptions.NeneException;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Default implementation of {@link PermissionContext}
+ */
+public final class PermissionContextImpl implements PermissionContext {
+ private final Permissions mPermissions;
+ private final Set<String> mGrantedPermissions = new HashSet<>();
+ private final Set<String> mDeniedPermissions = new HashSet<>();
+
+ PermissionContextImpl(Permissions permissions) {
+ mPermissions = permissions;
+ }
+
+ Set<String> grantedPermissions() {
+ return mGrantedPermissions;
+ }
+
+ Set<String> deniedPermissions() {
+ return mDeniedPermissions;
+ }
+
+ /**
+ * See {@link Permissions#withPermission(String...)}
+ */
+ public PermissionContextImpl withPermission(String... permissions) {
+ for (String permission : permissions) {
+ if (mDeniedPermissions.contains(permission)) {
+ throw new NeneException(
+ permission + " cannot be required to be both granted and denied");
+ }
+ }
+
+ mGrantedPermissions.addAll(Arrays.asList(permissions));
+
+ mPermissions.applyPermissions();
+
+ return this;
+ }
+
+ /**
+ * See {@link Permissions#withoutPermission(String...)}
+ */
+ public PermissionContextImpl withoutPermission(String... permissions) {
+ for (String permission : permissions) {
+ if (mGrantedPermissions.contains(permission)) {
+ throw new NeneException(
+ permission + " cannot be required to be both granted and denied");
+ }
+ }
+
+ mDeniedPermissions.addAll(Arrays.asList(permissions));
+
+ mPermissions.applyPermissions();
+
+ return this;
+ }
+
+ @Override
+ public void close() {
+ Permissions.sInstance.undoPermission(this);
+ }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java
new file mode 100644
index 0000000..7334a2e
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.permissions;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.packages.Package;
+import com.android.bedstead.nene.packages.PackageReference;
+import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.utils.ShellCommandUtils;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Permission manager for tests. */
+public class Permissions {
+
+ private static final String LOG_TAG = "Permissions";
+
+ private List<PermissionContextImpl> mPermissionContexts = new ArrayList<>();
+ private static final TestApis sTestApis = new TestApis();
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+ private static final PackageManager sPackageManager = sContext.getPackageManager();
+ private static final PackageReference sInstrumentedPackage =
+ sTestApis.packages().find(sContext.getPackageName());
+ private static final UserReference sUser = sTestApis.users().instrumented();
+ private static final Package sShellPackage =
+ sTestApis.packages().find("com.android.shell").resolve();
+ private static final boolean SUPPORTS_ADOPT_SHELL_PERMISSIONS =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
+
+ // Permissions is a singleton as permission state must be application wide
+ public static final Permissions sInstance = new Permissions();
+
+ private Permissions() {
+
+ }
+
+ /**
+ * Enter a {@link PermissionContext} where the given permissions are granted.
+ *
+ * <p>If the permissions cannot be granted, and are not already granted, an exception will be
+ * thrown.
+ *
+ * <p>Recommended usage:
+ * {@code
+ *
+ * try (PermissionContext p = mTestApis.permissions().withPermission(PERMISSION1, PERMISSION2) {
+ * // Code which needs the permissions goes here
+ * }
+ * }
+ */
+ public PermissionContextImpl withPermission(String... permissions) {
+ PermissionContextImpl permissionContext = new PermissionContextImpl(this);
+ mPermissionContexts.add(permissionContext);
+
+ permissionContext.withPermission(permissions);
+
+ return permissionContext;
+ }
+
+ /**
+ * Enter a {@link PermissionContext} where the given permissions are not granted.
+ *
+ * <p>If the permissions cannot be denied, and are not already denied, an exception will be
+ * thrown.
+ *
+ * <p>Recommended usage:
+ * {@code
+ *
+ * try (PermissionContext p =
+ * mTestApis.permissions().withoutPermission(PERMISSION1, PERMISSION2) {
+ * // Code which needs the permissions goes here
+ * }
+ */
+ public PermissionContextImpl withoutPermission(String... permissions) {
+ PermissionContextImpl permissionContext = new PermissionContextImpl(this);
+ mPermissionContexts.add(permissionContext);
+
+ permissionContext.withoutPermission(permissions);
+
+ return permissionContext;
+ }
+
+ void undoPermission(PermissionContext permissionContext) {
+ mPermissionContexts.remove(permissionContext);
+ applyPermissions();
+ }
+
+ void applyPermissions() {
+ Package resolvedInstrumentedPackage = sInstrumentedPackage.resolve();
+
+ if (SUPPORTS_ADOPT_SHELL_PERMISSIONS) {
+ ShellCommandUtils.uiAutomation().dropShellPermissionIdentity();
+ }
+ Set<String> grantedPermissions = new HashSet<>();
+ Set<String> deniedPermissions = new HashSet<>();
+
+ for (PermissionContextImpl permissionContext : mPermissionContexts) {
+ for (String permission : permissionContext.grantedPermissions()) {
+ grantedPermissions.add(permission);
+ deniedPermissions.remove(permission);
+ }
+
+ for (String permission : permissionContext.deniedPermissions()) {
+ grantedPermissions.remove(permission);
+ deniedPermissions.add(permission);
+ }
+ }
+
+ Log.d(LOG_TAG, "Applying permissions granting: "
+ + grantedPermissions + " denying: " + deniedPermissions);
+
+ // We first try to use shell permissions, because they can be revoked/etc. much more easily
+
+ Set<String> adoptedShellPermissions = new HashSet<>();
+
+ for (String permission : grantedPermissions) {
+ Log.d(LOG_TAG , "Trying to grant " + permission);
+ if (resolvedInstrumentedPackage.grantedPermissions(sUser).contains(permission)) {
+ // Already granted, can skip
+ Log.d(LOG_TAG, permission + " already granted");
+ } else if (SUPPORTS_ADOPT_SHELL_PERMISSIONS
+ && sShellPackage.requestedPermissions().contains(permission)) {
+ adoptedShellPermissions.add(permission);
+ Log.d(LOG_TAG, "will adopt " + permission);
+ } else if (canGrantPermission(permission)) {
+ Log.d(LOG_TAG, "Granting " + permission);
+ sInstrumentedPackage.grantPermission(sUser, permission);
+ } else {
+ Log.d(LOG_TAG, "Can not grant " + permission);
+ removePermissionContextsUntilCanApply();
+ throw new NeneException("PermissionContext requires granting "
+ + permission + " but cannot.");
+ }
+ }
+
+ for (String permission : deniedPermissions) {
+ Log.d(LOG_TAG , "Trying to deny " + permission);
+ if (!resolvedInstrumentedPackage.grantedPermissions(sUser).contains(permission)) {
+ // Already denied, can skip
+ Log.d(LOG_TAG, permission + " already denied");
+ } else if (SUPPORTS_ADOPT_SHELL_PERMISSIONS
+ && !sShellPackage.requestedPermissions().contains(permission)) {
+ adoptedShellPermissions.add(permission);
+ Log.d(LOG_TAG, "will adopt " + permission);
+ } else { // We can't deny a permission to ourselves
+ Log.d(LOG_TAG, "Can not deny " + permission);
+ removePermissionContextsUntilCanApply();
+ throw new NeneException("PermissionContext requires denying "
+ + permission + " but cannot.");
+ }
+ }
+
+ if (!adoptedShellPermissions.isEmpty()) {
+ Log.d(LOG_TAG, "Adopting " + adoptedShellPermissions);
+ ShellCommandUtils.uiAutomation().adoptShellPermissionIdentity(
+ adoptedShellPermissions.toArray(new String[0]));
+ }
+ }
+
+ private void removePermissionContextsUntilCanApply() {
+ try {
+ mPermissionContexts.remove(mPermissionContexts.size() - 1);
+ applyPermissions();
+ } catch (NeneException e) {
+ // Suppress NeneException here as we may get a few as we pop through the stack
+ }
+ }
+
+ private boolean canGrantPermission(String permission) {
+ try {
+ PermissionInfo p = sPackageManager.getPermissionInfo(permission, /* flags= */ 0);
+ if ((p.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) > 0) {
+ return true;
+ }
+ if ((p.protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) > 0) {
+ return true;
+ }
+
+ return false;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/common/device-side/bedstead/nene/src/test/AndroidManifest.xml b/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
index 92c6409..1b0515f 100644
--- a/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
+++ b/common/device-side/bedstead/nene/src/test/AndroidManifest.xml
@@ -22,6 +22,8 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<application
android:label="Nene Tests"
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/permissions/PermissionsTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/permissions/PermissionsTest.java
new file mode 100644
index 0000000..c1e6c4a
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/permissions/PermissionsTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.bedstead.nene.permissions;
+
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.os.Build;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.exceptions.NeneException;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PermissionsTest {
+
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+ private static final String PERMISSION_HELD_BY_SHELL =
+ "android.permission.INTERACT_ACROSS_PROFILES";
+ private final TestApis mTestApis = new TestApis();
+
+ private static final String NON_EXISTING_PERMISSION = "permissionWhichDoesNotExist";
+
+ // We expect these permissions are listed in the Manifest
+ private static final String INSTALL_PERMISSION = "android.permission.CHANGE_WIFI_STATE";
+ private static final String DECLARED_PERMISSION_NOT_HELD_BY_SHELL =
+ "android.permission.INTERNET";
+
+ @Test
+ public void default_permissionIsNotGranted() {
+ assertThat(sContext.checkSelfPermission(PERMISSION_HELD_BY_SHELL))
+ .isEqualTo(PERMISSION_DENIED);
+ }
+
+ @Test
+ public void withPermission_shellPermission_permissionIsGranted() {
+ assumeTrue("assume shell identity is only available on Q+",
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q);
+
+ try (PermissionContext p =
+ mTestApis.permissions().withPermission(PERMISSION_HELD_BY_SHELL)) {
+ assertThat(sContext.checkSelfPermission(PERMISSION_HELD_BY_SHELL))
+ .isEqualTo(PERMISSION_GRANTED);
+ }
+ }
+
+ @Test
+ public void withoutPermission_alreadyGranted_androidPreQ_throwsException() {
+ assumeTrue("assume shell identity is only available on Q+",
+ Build.VERSION.SDK_INT < Build.VERSION_CODES.Q);
+
+ assertThrows(NeneException.class,
+ () -> mTestApis.permissions().withoutPermission(
+ DECLARED_PERMISSION_NOT_HELD_BY_SHELL));
+ }
+
+ @Test
+ public void withoutPermission_permissionIsNotGranted() {
+ assumeTrue("assume shell identity is only available on Q+",
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q);
+
+ try (PermissionContext p = mTestApis.permissions().withPermission(PERMISSION_HELD_BY_SHELL);
+ PermissionContext p2 = mTestApis.permissions().withoutPermission(
+ PERMISSION_HELD_BY_SHELL)) {
+
+ assertThat(sContext.checkSelfPermission(PERMISSION_HELD_BY_SHELL))
+ .isEqualTo(PERMISSION_DENIED);
+ }
+ }
+
+ @Test
+ public void autoclose_withoutPermission_permissionIsGrantedAgain() {
+ assumeTrue("assume shell identity is only available on Q+",
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q);
+
+ try (PermissionContext p =
+ mTestApis.permissions().withPermission(PERMISSION_HELD_BY_SHELL)) {
+ try (PermissionContext p2 =
+ mTestApis.permissions().withoutPermission(PERMISSION_HELD_BY_SHELL)) {
+ // Intentionally empty as we're testing that autoclosing restores the permission
+ }
+
+ assertThat(sContext.checkSelfPermission(PERMISSION_HELD_BY_SHELL))
+ .isEqualTo(PERMISSION_GRANTED);
+ }
+ }
+
+ @Test
+ public void withoutPermission_installPermission_androidPreQ_throwsException() {
+ assumeTrue("assume shell identity is only available on Q+",
+ Build.VERSION.SDK_INT < Build.VERSION_CODES.Q);
+
+ assertThrows(NeneException.class,
+ () -> mTestApis.permissions().withoutPermission(INSTALL_PERMISSION));
+ }
+
+ @Test
+ public void withoutPermission_permissionIsAlreadyGrantedInInstrumentedApp_permissionIsNotGranted() {
+ assumeTrue("assume shell identity is only available on Q+",
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q);
+
+ try (PermissionContext p =
+ mTestApis.permissions().withoutPermission(
+ DECLARED_PERMISSION_NOT_HELD_BY_SHELL)) {
+ assertThat(
+ sContext.checkSelfPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL))
+ .isEqualTo(PERMISSION_DENIED);
+ }
+ }
+
+ @Test
+ public void withoutPermission_permissionIsAlreadyGrantedInInstrumentedApp_androidPreQ_throwsException() {
+ assumeTrue("assume shell identity is only available on Q+",
+ Build.VERSION.SDK_INT < Build.VERSION_CODES.Q);
+
+ assertThrows(NeneException.class,
+ () -> mTestApis.permissions().withoutPermission(
+ DECLARED_PERMISSION_NOT_HELD_BY_SHELL));
+ }
+
+ @Test
+ public void withPermission_permissionIsAlreadyGrantedInInstrumentedApp_permissionIsGranted() {
+ try (PermissionContext p =
+ mTestApis.permissions().withPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL)) {
+ assertThat(
+ sContext.checkSelfPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL))
+ .isEqualTo(PERMISSION_GRANTED);
+ }
+ }
+
+ @Test
+ public void withPermission_nonExistingPermission_throwsException() {
+ assertThrows(NeneException.class,
+ () -> mTestApis.permissions().withPermission(NON_EXISTING_PERMISSION));
+ }
+
+ @Test
+ public void withoutPermission_nonExistingPermission_doesNotThrowException() {
+ try (PermissionContext p =
+ mTestApis.permissions().withoutPermission(NON_EXISTING_PERMISSION)) {
+ // Intentionally empty
+ }
+ }
+
+ @Test
+ public void withPermissionAndWithoutPermission_bothApplied() {
+ try (PermissionContext p = mTestApis.permissions().withPermission(PERMISSION_HELD_BY_SHELL)
+ .withoutPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL)) {
+
+ assertThat(sContext.checkSelfPermission(PERMISSION_HELD_BY_SHELL))
+ .isEqualTo(PERMISSION_GRANTED);
+ assertThat(sContext.checkSelfPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL))
+ .isEqualTo(PERMISSION_DENIED);
+ }
+ }
+
+ @Test
+ public void withoutPermissionAndWithPermission_bothApplied() {
+ try (PermissionContext p = mTestApis.permissions()
+ .withoutPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL)
+ .withPermission(PERMISSION_HELD_BY_SHELL)) {
+
+ assertThat(sContext.checkSelfPermission(PERMISSION_HELD_BY_SHELL))
+ .isEqualTo(PERMISSION_GRANTED);
+ assertThat(sContext.checkSelfPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL))
+ .isEqualTo(PERMISSION_DENIED);
+ }
+ }
+
+ @Test
+ public void withPermissionAndWithoutPermission_contradictoryPermissions_throwsException() {
+ assertThrows(NeneException.class, () -> mTestApis.permissions()
+ .withPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL)
+ .withoutPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL));
+ }
+
+ @Test
+ public void withoutPermissionAndWithPermission_contradictoryPermissions_throwsException() {
+ assertThrows(NeneException.class, () -> mTestApis.permissions()
+ .withoutPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL)
+ .withPermission(DECLARED_PERMISSION_NOT_HELD_BY_SHELL));
+ }
+
+ // TODO(scottjonathan): Once we can install the testapp without granting all runtime
+ // permissions, add a test that this works pre-Q
+}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java
index ce802cc..f0a2bf8 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/users/UserReferenceTest.java
@@ -16,6 +16,7 @@
package com.android.bedstead.nene.users;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.Build.VERSION.SDK_INT;
@@ -29,11 +30,9 @@
import android.content.Intent;
import android.os.Build;
-import androidx.test.platform.app.InstrumentationRegistry;
-
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.exceptions.NeneException;
-import com.android.compatibility.common.util.SystemUtil;
+import com.android.bedstead.nene.permissions.PermissionContext;
import com.android.eventlib.EventLogs;
import com.android.eventlib.events.activities.ActivityCreatedEvent;
@@ -46,30 +45,29 @@
private static final int NON_EXISTING_USER_ID = 10000;
private static final int USER_ID = NON_EXISTING_USER_ID;
private static final String USER_NAME = "userName";
- private static final Context sContext =
- InstrumentationRegistry.getInstrumentation().getContext();
private static final String TEST_ACTIVITY_NAME = "com.android.bedstead.nene.test.Activity";
- private final TestApis mTestApis = new TestApis();
+ private static final TestApis sTestApis = new TestApis();
+ private static final Context sContext = sTestApis.context().instrumentedContext();
@Test
public void id_returnsId() {
- assertThat(mTestApis.users().find(USER_ID).id()).isEqualTo(USER_ID);
+ assertThat(sTestApis.users().find(USER_ID).id()).isEqualTo(USER_ID);
}
@Test
public void userHandle_referencesId() {
- assertThat(mTestApis.users().find(USER_ID).userHandle().getIdentifier()).isEqualTo(USER_ID);
+ assertThat(sTestApis.users().find(USER_ID).userHandle().getIdentifier()).isEqualTo(USER_ID);
}
@Test
public void resolve_doesNotExist_returnsNull() {
- assertThat(mTestApis.users().find(NON_EXISTING_USER_ID).resolve()).isNull();
+ assertThat(sTestApis.users().find(NON_EXISTING_USER_ID).resolve()).isNull();
}
@Test
public void resolve_doesExist_returnsUser() {
- UserReference userReference = mTestApis.users().createUser().create();
+ UserReference userReference = sTestApis.users().createUser().create();
try {
assertThat(userReference.resolve()).isNotNull();
@@ -80,7 +78,7 @@
@Test
public void resolve_doesExist_userHasCorrectDetails() {
- UserReference userReference = mTestApis.users().createUser().name(USER_NAME).create();
+ UserReference userReference = sTestApis.users().createUser().name(USER_NAME).create();
try {
User user = userReference.resolve();
@@ -92,27 +90,27 @@
@Test
public void remove_userDoesNotExist_throwsException() {
- assertThrows(NeneException.class, () -> mTestApis.users().find(USER_ID).remove());
+ assertThrows(NeneException.class, () -> sTestApis.users().find(USER_ID).remove());
}
@Test
public void remove_userExists_removesUser() {
- UserReference user = mTestApis.users().createUser().create();
+ UserReference user = sTestApis.users().createUser().create();
user.remove();
- assertThat(mTestApis.users().all()).doesNotContain(user);
+ assertThat(sTestApis.users().all()).doesNotContain(user);
}
@Test
public void start_userDoesNotExist_throwsException() {
assertThrows(NeneException.class,
- () -> mTestApis.users().find(NON_EXISTING_USER_ID).start());
+ () -> sTestApis.users().find(NON_EXISTING_USER_ID).start());
}
@Test
public void start_userNotStarted_userIsStarted() {
- UserReference user = mTestApis.users().createUser().create().stop();
+ UserReference user = sTestApis.users().createUser().create().stop();
user.start();
@@ -125,7 +123,7 @@
@Test
public void start_userAlreadyStarted_doesNothing() {
- UserReference user = mTestApis.users().createUser().createAndStart();
+ UserReference user = sTestApis.users().createUser().createAndStart();
user.start();
@@ -139,12 +137,12 @@
@Test
public void stop_userDoesNotExist_throwsException() {
assertThrows(NeneException.class,
- () -> mTestApis.users().find(NON_EXISTING_USER_ID).stop());
+ () -> sTestApis.users().find(NON_EXISTING_USER_ID).stop());
}
@Test
public void stop_userStarted_userIsStopped() {
- UserReference user = mTestApis.users().createUser().createAndStart();
+ UserReference user = sTestApis.users().createUser().createAndStart();
user.stop();
@@ -157,7 +155,7 @@
@Test
public void stop_userNotStarted_doesNothing() {
- UserReference user = mTestApis.users().createUser().create().stop();
+ UserReference user = sTestApis.users().createUser().create().stop();
user.stop();
@@ -171,30 +169,29 @@
@Test
public void switchTo_userIsSwitched() {
assumeTrue(
- "Adopting Shell Permissions only works for Q+", SDK_INT >= Build.VERSION_CODES.Q);
- // TODO(scottjonathan): In this case we can probably grant the permission through adb?
+ "INTERACT_ACROSS_USERS_FULL is only usable by tests on Q+",
+ SDK_INT >= Build.VERSION_CODES.Q);
+ UserReference user = sTestApis.users().createUser().createAndStart();
- UserReference user = mTestApis.users().createUser().createAndStart();
- try {
- mTestApis.packages().find(sContext.getPackageName()).install(user);
+ try (PermissionContext p =
+ sTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+
+ sTestApis.packages().find(sContext.getPackageName()).install(user);
user.switchTo();
- SystemUtil.runWithShellPermissionIdentity(() -> {
- // for INTERACT_ACROSS_USERS
- Intent intent = new Intent();
- intent.setPackage(sContext.getPackageName());
- intent.setClassName(sContext.getPackageName(), TEST_ACTIVITY_NAME);
- intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
- sContext.startActivityAsUser(intent, user.userHandle());
+ Intent intent = new Intent();
+ intent.setPackage(sContext.getPackageName());
+ intent.setClassName(sContext.getPackageName(), TEST_ACTIVITY_NAME);
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+ sContext.startActivityAsUser(intent, user.userHandle());
- EventLogs<ActivityCreatedEvent> logs =
- ActivityCreatedEvent.queryPackage(sContext.getPackageName())
- .whereActivity().className().isEqualTo(TEST_ACTIVITY_NAME)
- .onUser(user.userHandle());
- assertThat(logs.poll()).isNotNull();
- });
+ EventLogs<ActivityCreatedEvent> logs =
+ ActivityCreatedEvent.queryPackage(sContext.getPackageName())
+ .whereActivity().className().isEqualTo(TEST_ACTIVITY_NAME)
+ .onUser(user.userHandle());
+ assertThat(logs.poll()).isNotNull();
} finally {
- mTestApis.users().system().switchTo();
+ sTestApis.users().system().switchTo();
user.remove();
}
}
@@ -202,10 +199,10 @@
@Test
public void stop_isWorkProfileOfCurrentUser_stops() {
UserType managedProfileType =
- mTestApis.users().supportedType(UserType.MANAGED_PROFILE_TYPE_NAME);
- UserReference profileUser = mTestApis.users().createUser()
+ sTestApis.users().supportedType(UserType.MANAGED_PROFILE_TYPE_NAME);
+ UserReference profileUser = sTestApis.users().createUser()
.type(managedProfileType)
- .parent(mTestApis.users().instrumented())
+ .parent(sTestApis.users().instrumented())
.createAndStart();
try {
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/CtsTouchUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/CtsTouchUtils.java
index 38c0c2a..5a552ed 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/CtsTouchUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/CtsTouchUtils.java
@@ -26,6 +26,7 @@
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import androidx.annotation.Nullable;
import androidx.test.rule.ActivityTestRule;
@@ -524,7 +525,7 @@
* @param viewGroup View group
*/
public static void emulateScrollToBottom(Instrumentation instrumentation,
- ActivityTestRule<?> activityTestRule, ViewGroup viewGroup) {
+ ActivityTestRule<?> activityTestRule, ViewGroup viewGroup) throws Throwable {
final int[] viewGroupOnScreenXY = new int[2];
viewGroup.getLocationOnScreen(viewGroupOnScreenXY);
@@ -540,6 +541,28 @@
emulatedX, emulatedStartY, 0, -swipeAmount, 300, 10);
next = new ViewStateSnapshot(viewGroup);
} while (!prev.equals(next));
+
+ // wait until the overscroll animation completes
+ final boolean[] redrawn = new boolean[1];
+ final boolean[] animationFinished = new boolean[1];
+ final ViewTreeObserver.OnDrawListener onDrawListener = () -> {
+ redrawn[0] = true;
+ };
+
+ activityTestRule.runOnUiThread(() -> {
+ viewGroup.getViewTreeObserver().addOnDrawListener(onDrawListener);
+ });
+ while (!animationFinished[0]) {
+ activityTestRule.runOnUiThread(() -> {
+ if (!redrawn[0]) {
+ animationFinished[0] = true;
+ }
+ redrawn[0] = false;
+ });
+ }
+ activityTestRule.runOnUiThread(() -> {
+ viewGroup.getViewTreeObserver().removeOnDrawListener(onDrawListener);
+ });
}
/**
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
index 0f4c62d..b6473b6 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
@@ -36,6 +36,7 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+// TODO(b/183395856): Remove once the remaining silent provisioning tests are removed.
public class SilentProvisioningTestManager {
private static final long TIMEOUT_SECONDS = 120L;
private static final String TAG = "SilentProvisioningTest";
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
index 77b059e..e948237 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
@@ -315,7 +315,6 @@
@Test
public void testNormalProcessCannotAccessOtherAppExternalDataDir() throws Exception {
- assumeThatFuseDataIsolationIsEnabled(getDevice());
new InstallMultiple().addFile(APPA_APK).run();
new InstallMultiple().addFile(APPB_APK).run();
@@ -417,13 +416,6 @@
+ " " + packageName));
}
- private static void assumeThatFuseDataIsolationIsEnabled(ITestDevice device)
- throws DeviceNotAvailableException {
- assumeThat(device.executeShellCommand(
- "getprop persist.sys.vold_app_data_isolation_enabled").trim(),
- is("true"));
- }
-
private boolean isFbeModeEmulated() throws Exception {
String mode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
if (mode.equals(FBE_MODE_EMULATED)) {
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
index 9b5424b..51395bd 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
@@ -81,9 +81,17 @@
public void testMountPointsNotReadable() throws Exception {
final String userId = Integer.toString(android.os.Process.myUid() / 100000);
final List<File> mountPaths = getMountPaths();
+ final String packageName = getContext().getPackageName();
for (File path : mountPaths) {
if (path.getAbsolutePath().startsWith("/mnt/")
|| path.getAbsolutePath().startsWith("/storage/")) {
+ if (path.getAbsolutePath().endsWith("obb/" + packageName) ||
+ path.getAbsolutePath().endsWith("data/" + packageName)) {
+ // It happens when app data isolation is enabled, obb and data dir will
+ // be mounted in app's mount namespace.
+ // It's package's own obb / data dir, we allow it.
+ continue;
+ }
// Mount points could be multi-user aware, so try probing both
// top level and user-specific directory.
final File userPath = new File(path, userId);
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 1e07486..8c4f29c 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
@@ -58,7 +58,6 @@
import com.google.common.io.ByteStreams;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -525,7 +524,6 @@
assertEquals("0", queryForSingleColumn(red, MediaColumns.IS_TRASHED));
}
- @Ignore("b/181333720")
@Test
public void testMediaEscalation_RequestFavorite() throws Exception {
doMediaEscalation_RequestFavorite(MediaStorageTest::createAudio);
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
index a43bbe7..73f56dd 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
@@ -85,9 +85,14 @@
public void testMountPointsNotWritable() throws Exception {
final String userId = Integer.toString(android.os.Process.myUid() / 100000);
final List<File> mountPaths = getMountPaths();
+ final String packageName = getContext().getPackageName();
for (File path : mountPaths) {
if (path.getAbsolutePath().startsWith("/mnt/")
|| path.getAbsolutePath().startsWith("/storage/")) {
+ if (path.getAbsolutePath().endsWith(packageName)) {
+ // It's package's own obb / data dir, we allow it.
+ continue;
+ }
// Mount points could be multi-user aware, so try probing both
// top level and user-specific directory.
final File userPath = new File(path, userId);
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.bp
index fa03865..2744c6f 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.bp
@@ -22,6 +22,7 @@
sdk_version: "test_current",
static_libs: [
"CtsWriteExternalStorageWriteGiftLib",
+ "androidx.appcompat_appcompat",
"androidx.test.rules",
"compatibility-device-util-axt",
],
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 827b440..8720537 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
@@ -36,6 +36,7 @@
import android.test.AndroidTestCase;
import android.util.Log;
+import androidx.core.os.BuildCompat;
import com.android.cts.externalstorageapp.CommonExternalStorageTest;
import java.io.File;
@@ -263,6 +264,12 @@
* Verify that .nomedia is created correctly.
*/
public void testVerifyNoMediaCreated() throws Exception {
+ boolean expectNoMediaFileExists = true;
+ if (BuildCompat.isAtLeastS()) {
+ // All package specific paths will be in app's mount namespace, and it cannot
+ // access its parent to check .nomedia file.
+ expectNoMediaFileExists = false;
+ }
for (File file : getAllPackageSpecificPathsExceptMedia(getContext())) {
deleteContents(file);
}
@@ -290,9 +297,12 @@
path = path.getParentFile();
}
- if (!found) {
+ if (expectNoMediaFileExists && !found) {
fail("Missing .nomedia file above package-specific directory " + start
+ "; gave up at " + path);
+ } else if (!expectNoMediaFileExists && found) {
+ fail(".nomedia file should not be exists due to app data isolation, but found " +
+ " in package-specific directory " + start + "; gave up at " + path);
}
}
}
@@ -309,6 +319,7 @@
final String userId = Integer.toString(android.os.Process.myUid() / 100000);
final List<File> mountPaths = getMountPaths();
+ final String packageName = getContext().getPackageName();
for (File path : mountPaths) {
// Mount points could be multi-user aware, so try probing both top
// level and user-specific directory.
@@ -326,6 +337,11 @@
|| path.getAbsolutePath().endsWith("/Android/obb")) {
assertDirNoWriteAccess(path);
} else {
+ if (path.getAbsolutePath().endsWith(packageName)) {
+ // It's package's own obb / data dir, it's not a normal mount point
+ // and we don't need to check the access.
+ continue;
+ }
assertDirReadWriteAccess(path);
assertDirReadWriteAccess(buildCommonChildDirs(path));
}
diff --git a/hostsidetests/blobstore/OWNERS b/hostsidetests/blobstore/OWNERS
index bf870975..3a47c48 100644
--- a/hostsidetests/blobstore/OWNERS
+++ b/hostsidetests/blobstore/OWNERS
@@ -1,2 +1,2 @@
-# Bug component: 95221
+# Bug component: 533114
include platform/frameworks/base:/apex/blobstore/OWNERS
diff --git a/hostsidetests/car/AndroidTest.xml b/hostsidetests/car/AndroidTest.xml
index d72f51a..3699a2b 100644
--- a/hostsidetests/car/AndroidTest.xml
+++ b/hostsidetests/car/AndroidTest.xml
@@ -19,6 +19,10 @@
<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.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsCarApp.apk" />
+ </target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
<option name="jar" value="CtsCarHostTestCases.jar" />
<option name="runtime-hint" value="1m" />
diff --git a/hostsidetests/car/app/Android.bp b/hostsidetests/car/app/Android.bp
index 0c54753..0a0dd6a 100644
--- a/hostsidetests/car/app/Android.bp
+++ b/hostsidetests/car/app/Android.bp
@@ -21,4 +21,13 @@
defaults: ["cts_defaults"],
srcs: ["src/**/*.java"],
sdk_version: "current",
+
+ enforce_uses_libs: false,
+ static_libs: [
+ "android.frameworks.automotive.powerpolicy-V1-java",
+ "android.hardware.automotive.vehicle-V2.0-java",
+ "androidx.test.rules",
+ ],
+
+ libs: ["android.car"],
}
diff --git a/hostsidetests/car/app/AndroidManifest.xml b/hostsidetests/car/app/AndroidManifest.xml
index 17b2685..cd8e6b7 100755
--- a/hostsidetests/car/app/AndroidManifest.xml
+++ b/hostsidetests/car/app/AndroidManifest.xml
@@ -18,7 +18,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.car.cts.app">
+ <uses-permission android:name="android.car.permission.CAR_POWER"/>
+ <uses-permission android:name="android.car.permission.READ_CAR_POWER_POLICY"/>
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+ <uses-permission android:name="android.permission.STORAGE_INTERNAL"/>
+
<application>
+ <activity android:name=".PowerPolicyTestActivity"
+ android:launchMode="singleTask"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
<activity android:name=".SimpleActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestActivity.java b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestActivity.java
new file mode 100644
index 0000000..1bab9f6
--- /dev/null
+++ b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestActivity.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app;
+
+import android.app.Activity;
+import android.car.Car;
+import android.car.hardware.power.CarPowerManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * To test car power policy:
+ * <pre class="prettyprint">
+ * adb shell am force-stop android.car.cts.app
+ * adb shell am start -n android.car.cts.app/.PowerPolicyTestActivity \
+ * --es "powerpolicy" "testcase,action[,data]"
+ * - testcase: suite | 1 | 2 | 3 | 4 | 5 | 6
+ * - action: start | end | dumpstate | dumppolicy | applypolicy | closefile
+ * - data: policyId
+ * </pre>
+ */
+public final class PowerPolicyTestActivity extends Activity {
+ private static final String TAG = PowerPolicyTestActivity.class.getSimpleName();
+ private static final String SHARED_DATA_FILE = "/storage/emulated/obb/PowerPolicyData.txt";
+
+ private Car mCarApi;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private CarPowerManager mPowerManager;
+ private PrintWriter mPrintWriter;
+
+ @Nullable
+ public CarPowerManager getPowerManager() {
+ synchronized (mLock) {
+ return mPowerManager;
+ }
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ Log.d(TAG, "onNewIntent");
+ Bundle extras = intent.getExtras();
+ if (extras == null) {
+ Log.w(TAG, "onNewIntent: empty extras");
+ return;
+ }
+ PowerPolicyTestCommand cmd = PowerPolicyTestClient.parseCommand(extras);
+ if (cmd == null) {
+ Log.w(TAG, "onNewIntent: invalid power policy test command");
+ return;
+ }
+ cmd.setCar(mCarApi);
+ cmd.setPrintWriter(mPrintWriter);
+ PowerPolicyTestClient.handleCommand(cmd);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ initCarApi();
+ Context ctx = getApplicationContext();
+ try {
+ mPrintWriter = new PrintWriter(new File(ctx.getFilesDir(), SHARED_DATA_FILE));
+ } catch (IOException e) {
+ Log.e(TAG, "onCreate: failed to open PowerPolicyData.txt");
+ mPrintWriter = null;
+ }
+
+ Log.d(TAG, "onCreate");
+ onNewIntent(getIntent());
+ }
+
+ private void initCarApi() {
+ if (mCarApi != null && mCarApi.isConnected()) {
+ mCarApi.disconnect();
+ mCarApi = null;
+ }
+ mCarApi = Car.createCar(this, null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
+ (Car car, boolean ready) -> {
+ initManagers(car, ready);
+ });
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.d(TAG, "onDestroy");
+ if (mCarApi != null) {
+ mCarApi.disconnect();
+ }
+ super.onDestroy();
+ }
+
+ private void initManagers(Car car, boolean ready) {
+ synchronized (mLock) {
+ if (ready) {
+ mPowerManager = (CarPowerManager) car.getCarManager(
+ android.car.Car.POWER_SERVICE);
+ Log.d(TAG, "initManagers() completed");
+ } else {
+ mPowerManager = null;
+ Log.wtf(TAG, "initManagers() set to be null");
+ }
+ }
+ }
+}
diff --git a/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestClient.java b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestClient.java
new file mode 100644
index 0000000..71ffda0
--- /dev/null
+++ b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestClient.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app;
+
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+
+public final class PowerPolicyTestClient {
+ private static final String TAG = PowerPolicyTestClient.class.getSimpleName();
+
+ private static final String POWERPOLICY_TEST_CMD_IDENTIFIER = "powerpolicy";
+ private static final String TEST_CMD_START = "start";
+ private static final String TEST_CMD_END = "end";
+ private static final String TEST_CMD_DUMP_STATE = "dumpstate";
+ private static final String TEST_CMD_DUMP_POLICY = "dumppolicy";
+ private static final String TEST_CMD_APPLY_POLICY = "applypolicy";
+ private static final String TEST_CMD_CLOSE_DATAFILE = "closefile";
+
+ private static PowerPolicyTestClient sPowerPolicyTestClient = new PowerPolicyTestClient();
+
+ private long mClientStartTime;
+
+ // This method is not intended for multi-threaded calls.
+ public static void handleCommand(PowerPolicyTestCommand cmd) {
+ switch (cmd.getType()) {
+ case START:
+ if (sPowerPolicyTestClient != null) {
+ Log.w(TAG, "can not restart the test without ending the previous test first");
+ return;
+ }
+ break;
+ default:
+ if (sPowerPolicyTestClient == null) {
+ Log.w(TAG, "execute test start command first");
+ return;
+ }
+ break;
+ }
+ cmd.execute(sPowerPolicyTestClient);
+
+ if (cmd.getType() == PowerPolicyTestCommand.TestCommandType.END) {
+ sPowerPolicyTestClient = null;
+ }
+ }
+
+ public static PowerPolicyTestCommand parseCommand(Bundle intentExtras) {
+ String testcase;
+ String action;
+ String data;
+ PowerPolicyTestCommand cmd = null;
+ String powertest = intentExtras.getString(POWERPOLICY_TEST_CMD_IDENTIFIER);
+ if (powertest == null) {
+ Log.d(TAG, "empty power test command");
+ return cmd;
+ }
+
+ String[] tokens = powertest.split(",");
+ int paramCount = tokens.length;
+ if (paramCount != 2 && paramCount != 3) {
+ throw new IllegalArgumentException("invalid command syntax");
+ }
+
+ testcase = tokens[0];
+ action = tokens[1];
+ if (paramCount == 3) {
+ data = tokens[2];
+ } else {
+ data = null;
+ }
+
+ switch (testcase) {
+ case TEST_CMD_START:
+ cmd = new PowerPolicyTestCommand.StartTestcaseCommand(testcase);
+ break;
+ case TEST_CMD_END:
+ cmd = new PowerPolicyTestCommand.EndTestcaseCommand(testcase);
+ break;
+ case TEST_CMD_DUMP_STATE:
+ cmd = new PowerPolicyTestCommand.DumpStateCommand(testcase);
+ break;
+ case TEST_CMD_DUMP_POLICY:
+ cmd = new PowerPolicyTestCommand.DumpPolicyCommand(testcase);
+ break;
+ case TEST_CMD_APPLY_POLICY:
+ if (paramCount != 3) {
+ throw new IllegalArgumentException("invalid command syntax");
+ }
+ cmd = new PowerPolicyTestCommand.ApplyPolicyCommand(testcase);
+ cmd.mPolicyId = data;
+ break;
+ case TEST_CMD_CLOSE_DATAFILE:
+ cmd = new PowerPolicyTestCommand.CloseDataFileCommand(testcase);
+ break;
+ default:
+ throw new IllegalArgumentException("invalid power policy test command: "
+ + action);
+ }
+
+ Log.i(TAG, "testcase=" + testcase + ", command=" + action);
+ return cmd;
+ }
+
+ public void cleanup() {
+ //TODO(b/183134882): add any necessary cleanup activities here
+ }
+
+ public void registerAndGo() {
+ mClientStartTime = SystemClock.uptimeMillis();
+ //TODO(b/183134882): here is the place to add listeners
+ }
+}
diff --git a/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestCommand.java b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestCommand.java
new file mode 100644
index 0000000..82d01db
--- /dev/null
+++ b/hostsidetests/car/app/src/android/car/cts/app/PowerPolicyTestCommand.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.app;
+
+import android.car.Car;
+import android.car.hardware.power.CarPowerManager;
+import android.car.hardware.power.CarPowerPolicy;
+import android.util.Log;
+
+import java.io.PrintWriter;
+
+public abstract class PowerPolicyTestCommand {
+ private static final String TAG = PowerPolicyTestCommand.class.getSimpleName();
+
+ private final String mTestcase;
+ private final TestCommandType mType;
+
+ protected String mPolicyId;
+ protected Car mCar;
+ protected CarPowerManager mCarPowerManager;
+ protected PrintWriter mPrintWriter;
+
+ PowerPolicyTestCommand(String tc, TestCommandType type) {
+ mTestcase = tc;
+ mType = type;
+ }
+
+ void setCar(Car c) {
+ mCar = c;
+ mCarPowerManager = (CarPowerManager) mCar.getCarManager(Car.POWER_SERVICE);
+ }
+
+ String getTestcase() {
+ return mTestcase;
+ }
+
+ Car getCar() {
+ return mCar;
+ }
+
+ TestCommandType getType() {
+ return mType;
+ }
+
+ PrintWriter getPrintWriter() {
+ return mPrintWriter;
+ }
+
+ void setPrintWriter(PrintWriter fw) {
+ mPrintWriter = fw;
+ }
+
+ abstract void execute(PowerPolicyTestClient testClient);
+
+ enum TestCommandType {
+ START,
+ END,
+ DUMP_STATE,
+ DUMP_POLICY,
+ APPLY_POLICY,
+ CLOSE_DATAFILE
+ }
+
+ static final class StartTestcaseCommand extends PowerPolicyTestCommand {
+ StartTestcaseCommand(String tc) {
+ super(tc, TestCommandType.START);
+ }
+
+ void execute(PowerPolicyTestClient testClient) {
+ testClient.registerAndGo();
+ }
+ }
+
+ static final class EndTestcaseCommand extends PowerPolicyTestCommand {
+ EndTestcaseCommand(String tc) {
+ super(tc, TestCommandType.END);
+ }
+
+ @Override
+ void execute(PowerPolicyTestClient testClient) {
+ mPrintWriter.flush();
+ testClient.cleanup();
+ }
+ }
+
+ static final class DumpStateCommand extends PowerPolicyTestCommand {
+ DumpStateCommand(String tc) {
+ super(tc, TestCommandType.DUMP_STATE);
+ }
+
+ @Override
+ void execute(PowerPolicyTestClient testClient) {
+ int curState = mCarPowerManager.getPowerState();
+ mPrintWriter.printf("%s: Current Power State: %s\n", getTestcase(), curState);
+ Log.d(TAG, "Current Power State: " + curState);
+ }
+ }
+
+ static final class DumpPolicyCommand extends PowerPolicyTestCommand {
+ DumpPolicyCommand(String tc) {
+ super(tc, TestCommandType.DUMP_POLICY);
+ }
+
+ @Override
+ void execute(PowerPolicyTestClient testClient) {
+ CarPowerPolicy cpp = mCarPowerManager.getCurrentPowerPolicy();
+ if (cpp == null) {
+ Log.d(TAG, "null current power policy");
+ return;
+ }
+ String policyId = cpp.getPolicyId();
+ int[] enabledComponents = cpp.getEnabledComponents();
+ int[] disabledComponents = cpp.getDisabledComponents();
+
+ mPrintWriter.printf("%s: Current Power Policy: id=%s", getTestcase(), policyId);
+ mPrintWriter.printf(", enabledComponents=[");
+ for (int enabled : enabledComponents) {
+ mPrintWriter.printf("%d ", enabled);
+ }
+ mPrintWriter.printf("], disabledComponents=[");
+ for (int disabled : disabledComponents) {
+ mPrintWriter.printf("%d ", disabled);
+ }
+ mPrintWriter.println("]");
+ Log.d(TAG, "Dumped Policy Id: " + policyId);
+ }
+ }
+
+ static final class ApplyPolicyCommand extends PowerPolicyTestCommand {
+ ApplyPolicyCommand(String tc) {
+ super(tc, TestCommandType.APPLY_POLICY);
+ }
+
+ @Override
+ void execute(PowerPolicyTestClient testClient) {
+ if (mPolicyId == null) {
+ Log.w(TAG, "missing policy id for applying policy");
+ return;
+ }
+
+ mCarPowerManager.applyPowerPolicy(mPolicyId);
+ mPrintWriter.printf("%s : Apply Power Policy:%s\n", getTestcase(), mPolicyId);
+ Log.d(TAG, "apply policy with Id: " + mPolicyId);
+ }
+ }
+
+ static final class CloseDataFileCommand extends PowerPolicyTestCommand {
+ CloseDataFileCommand(String tc) {
+ super(tc, TestCommandType.CLOSE_DATAFILE);
+ }
+
+ @Override
+ void execute(PowerPolicyTestClient testClient) {
+ mPrintWriter.close();
+ Log.d(TAG, "close the data file");
+ }
+ }
+}
+
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/DelegateTestUtils.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/DelegateTestUtils.java
index 7070841..330fa98 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/DelegateTestUtils.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/DelegateTestUtils.java
@@ -15,17 +15,30 @@
*/
package com.android.cts.delegate;
+import static android.app.admin.SecurityLog.TAG_KEY_DESTRUCTION;
+import static android.app.admin.SecurityLog.TAG_KEY_GENERATED;
+
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.fail;
import android.app.admin.DelegatedAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.app.admin.NetworkEvent;
+import android.app.admin.SecurityLog.SecurityEvent;
import android.content.Context;
import android.content.Intent;
+import android.os.Process;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
import android.test.MoreAsserts;
+import android.util.Log;
import junit.framework.Assert;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -35,12 +48,25 @@
* Utils class for delegation tests.
*/
public class DelegateTestUtils {
+ // Indices of various fields in SecurityEvent payload.
+ private static final int SUCCESS_INDEX = 0;
+ private static final int ALIAS_INDEX = 1;
+ private static final int UID_INDEX = 2;
+
+ // Value that indicates success in events that have corresponding field in their payload.
+ private static final int SUCCESS_VALUE = 1;
@FunctionalInterface
public interface ExceptionRunnable {
void run() throws Exception;
}
+ /**
+ * A receiver for listening for network logs.
+ *
+ * To use this the sBatchCountDown must be assigned before generating logs.
+ * The receiver will ignore events until sBatchCountDown is assigned.
+ */
public static class NetworkLogsReceiver extends DelegatedAdminReceiver {
private static final long TIMEOUT_MIN = 1;
@@ -51,6 +77,12 @@
@Override
public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
int networkLogsCount) {
+ if (sBatchCountDown == null) {
+ // If the latch is not set then nothing will be using the receiver to examine
+ // the logs. Leave the logs unread.
+ return;
+ }
+
DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
final List<NetworkEvent> events = dpm.retrieveNetworkLogs(null, batchToken);
if (events == null || events.size() == 0) {
@@ -78,7 +110,8 @@
try {
r.run();
} catch (Throwable e) {
- Assert.assertTrue("Expected " + expectedExceptionType.getName() + " but caught " + e,
+ Assert.assertTrue("Expected " + expectedExceptionType.getName() + " but caught:"
+ + "\n" + Log.getStackTraceString(e) + "\nTest exception:\n",
expectedExceptionType.isAssignableFrom(e.getClass()));
if (expectedExceptionMessageRegex != null) {
MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage());
@@ -87,4 +120,92 @@
}
Assert.fail("Expected " + expectedExceptionType.getName() + " was not thrown");
}
+
+ /**
+ * Generates a key for the given key alias, asserts it was created successfully
+ */
+ public static void testGenerateKey(String keyAlias) throws Exception {
+ final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
+ generator.initialize(
+ new KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_SIGN).build());
+ final KeyPair keyPair = generator.generateKeyPair();
+ assertThat(keyPair).isNotNull();
+ }
+
+ /**
+ * Deletes a key for the given key alias
+ */
+ public static void deleteKey(String keyAlias) throws Exception {
+ final KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
+ ks.load(null);
+ ks.deleteEntry(keyAlias);
+ }
+
+
+ /**
+ * Fetches the available security events
+ */
+ public static List<SecurityEvent> getSecurityEvents(DevicePolicyManager dpm)
+ throws Exception {
+ List<SecurityEvent> events = null;
+ // Retry once after seeping for 1 second, in case "dpm force-security-logs" hasn't taken
+ // effect just yet.
+ for (int i = 0; i < 5 && events == null; i++) {
+ events = dpm.retrieveSecurityLogs(null);
+ if (events == null) Thread.sleep(1000);
+ }
+
+ return events;
+ }
+
+ /**
+ * Verifies that the expected keystore events generated by {@link #testGenerateKey} are
+ * present
+ */
+ public static void verifyKeystoreEventsPresent(String generatedKeyAlias,
+ List<SecurityEvent> securityEvents) {
+ // STOPSHIP(b/183201685): re-enable when KeyStore2 logs these events.
+ /*
+ int receivedKeyGenerationEvents = 0;
+ int receivedKeyDeletionEvents = 0;
+
+ for (final SecurityEvent currentEvent : securityEvents) {
+ if (currentEvent.getTag() == TAG_KEY_GENERATED) {
+ verifyKeyEvent(currentEvent, generatedKeyAlias);
+ receivedKeyGenerationEvents++;
+ }
+
+ if (currentEvent.getTag() == TAG_KEY_DESTRUCTION) {
+ verifyKeyEvent(currentEvent, generatedKeyAlias);
+ receivedKeyDeletionEvents++;
+ }
+ }
+
+ assertThat(receivedKeyGenerationEvents).isEqualTo(1);
+ assertThat(receivedKeyDeletionEvents).isEqualTo(1);
+ */
+ }
+
+ /**
+ * Verifies that a security event represents a successful key modification event for
+ * keyAlias
+ */
+ private static void verifyKeyEvent(SecurityEvent event, String keyAlias) {
+ assertThat(getInt(event, SUCCESS_INDEX)).isEqualTo(SUCCESS_VALUE);
+ assertThat(getString(event, ALIAS_INDEX)).contains(keyAlias);
+ assertThat(getInt(event, UID_INDEX)).isEqualTo(Process.myUid());
+ }
+
+ private static Object getDatum(SecurityEvent event, int index) {
+ final Object[] dataArray = (Object[]) event.getData();
+ return dataArray[index];
+ }
+
+ private static String getString(SecurityEvent event, int index) {
+ return (String) getDatum(event, index);
+ }
+
+ private static int getInt(SecurityEvent event, int index) {
+ return (Integer) getDatum(event, index);
+ }
}
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/SecurityLoggingDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/SecurityLoggingDelegateTest.java
new file mode 100644
index 0000000..e924d41
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/SecurityLoggingDelegateTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.delegate;
+
+import static com.android.cts.delegate.DelegateTestUtils.assertExpectException;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.SecurityLog.SecurityEvent;
+import android.content.Context;
+import android.support.test.uiautomator.UiDevice;
+import android.test.InstrumentationTestCase;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.List;
+
+/**
+ * Tests that a delegate app with DELEGATION_SECURITY_LOGGING is able to control and access
+ * security logging.
+ */
+public class SecurityLoggingDelegateTest extends InstrumentationTestCase {
+
+ private static final String TAG = "SecurityLoggingDelegateTest";
+
+ private Context mContext;
+ private DevicePolicyManager mDpm;
+ private UiDevice mDevice;
+
+ private static final String GENERATED_KEY_ALIAS = "generated_key_alias";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mContext = getInstrumentation().getContext();
+ mDpm = mContext.getSystemService(DevicePolicyManager.class);
+ }
+
+ public void testCannotAccessApis()throws Exception {
+ assertExpectException(SecurityException.class, null,
+ () -> mDpm.isSecurityLoggingEnabled(null));
+
+ assertExpectException(SecurityException.class, null,
+ () -> mDpm.setSecurityLoggingEnabled(null, true));
+
+ assertExpectException(SecurityException.class, null,
+ () -> mDpm.retrieveSecurityLogs(null));
+ }
+
+ /**
+ * Test: Test enabling security logging.
+ * This test has a side effect: security logging is enabled after its execution.
+ */
+ public void testEnablingSecurityLogging() {
+ mDpm.setSecurityLoggingEnabled(null, true);
+
+ assertThat(mDpm.isSecurityLoggingEnabled(null)).isTrue();
+ }
+
+ /**
+ * Generates security events related to Keystore
+ */
+ public void testGenerateLogs() throws Exception {
+ try {
+ DelegateTestUtils.testGenerateKey(GENERATED_KEY_ALIAS);
+ } finally {
+ DelegateTestUtils.deleteKey(GENERATED_KEY_ALIAS);
+ }
+ }
+
+ /**
+ * Test: retrieves security logs and verifies that all events generated as a result of host
+ * side actions and by {@link #testGenerateLogs()} are there.
+ */
+ public void testVerifyGeneratedLogs() throws Exception {
+ final List<SecurityEvent> events = DelegateTestUtils.getSecurityEvents(mDpm);
+ DelegateTestUtils.verifyKeystoreEventsPresent(GENERATED_KEY_ALIAS, events);
+ }
+
+ /**
+ * Test: retrieving security logs should be rate limited - subsequent attempts should return
+ * null.
+ */
+ public void testSecurityLoggingRetrievalRateLimited() {
+ final List<SecurityEvent> logs = mDpm.retrieveSecurityLogs(null);
+ // if logs is null it means that that attempt was rate limited => test PASS
+ if (logs != null) {
+ assertThat(mDpm.retrieveSecurityLogs(null)).isNull();
+ assertThat(mDpm.retrieveSecurityLogs(null)).isNull();
+ }
+ }
+
+ /**
+ * Test: Test disaling security logging.
+ * This test has a side effect: security logging is disabled after its execution.
+ */
+ public void testDisablingSecurityLogging() {
+ mDpm.setSecurityLoggingEnabled(null, false);
+
+ assertThat(mDpm.isSecurityLoggingEnabled(null)).isFalse();
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/WorkProfileSecurityLoggingDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/WorkProfileSecurityLoggingDelegateTest.java
new file mode 100644
index 0000000..e660d1b
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/WorkProfileSecurityLoggingDelegateTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.delegate;
+
+import static com.android.cts.delegate.DelegateTestUtils.assertExpectException;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.SecurityLog.SecurityEvent;
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class WorkProfileSecurityLoggingDelegateTest {
+
+ private static final String TAG = "WorkProfileSecurityLoggingDelegateTest";
+ private static final String CTS_APP_PACKAGE_NAME = "com.android.cts.delegate";
+ private static final String GENERATED_KEY_ALIAS = "generated_key_alias";
+
+ private Context mContext;
+ private DevicePolicyManager mDpm;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getContext();
+ mDpm = mContext.getSystemService(DevicePolicyManager.class);
+ }
+
+ @Test
+ public void testCannotAccessApis() {
+ assertExpectException(SecurityException.class, null,
+ () -> mDpm.isSecurityLoggingEnabled(null));
+
+ assertExpectException(SecurityException.class, null,
+ () -> mDpm.setSecurityLoggingEnabled(null, true));
+
+ assertExpectException(SecurityException.class, null,
+ () -> mDpm.retrieveSecurityLogs(null));
+ }
+
+ /**
+ * Test: Test enabling security logging.
+ * This test has a side effect: security logging is enabled after its execution.
+ */
+ @Test
+ public void testEnablingSecurityLogging() {
+ mDpm.setSecurityLoggingEnabled(null, true);
+
+ assertThat(mDpm.isSecurityLoggingEnabled(null)).isTrue();
+ }
+
+ /**
+ * Generates security events related to Keystore
+ */
+ @Test
+ public void testGenerateLogs() throws Exception {
+ try {
+ DelegateTestUtils.testGenerateKey(GENERATED_KEY_ALIAS);
+ } finally {
+ DelegateTestUtils.deleteKey(GENERATED_KEY_ALIAS);
+ }
+ }
+
+ /**
+ * Test: retrieves security logs and verifies that all events generated as a result of host
+ * side actions and by {@link #testGenerateLogs()} are there.
+ */
+ @Test
+ public void testVerifyGeneratedLogs() throws Exception {
+ final List<SecurityEvent> events = DelegateTestUtils.getSecurityEvents(mDpm);
+ DelegateTestUtils.verifyKeystoreEventsPresent(GENERATED_KEY_ALIAS, events);
+ }
+
+ /**
+ * Test: retrieving security logs should be rate limited - subsequent attempts should return
+ * null.
+ */
+ @Test
+ public void testSecurityLoggingRetrievalRateLimited() {
+ final List<SecurityEvent> logs = mDpm.retrieveSecurityLogs(null);
+ // if logs is null it means that that attempt was rate limited => test PASS
+ if (logs != null) {
+ assertThat(mDpm.retrieveSecurityLogs(null)).isNull();
+ assertThat(mDpm.retrieveSecurityLogs(null)).isNull();
+ }
+ }
+
+ /**
+ * Test: Test disaling security logging.
+ * This test has a side effect: security logging is disabled after its execution.
+ */
+ @Test
+ public void testDisablingSecurityLogging() {
+ mDpm.setSecurityLoggingEnabled(null, false);
+
+ assertThat(mDpm.isSecurityLoggingEnabled(null)).isFalse();
+ }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
index 1221dd2..ba14081 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
@@ -35,6 +35,8 @@
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <!-- TODO(b/176993670): remove if DpmWrapper goes away -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<!-- Add a network security config that trusts user added CAs for tests -->
<application android:networkSecurityConfig="@xml/network_security_config"
@@ -129,6 +131,11 @@
<action android:name="android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE" />
</intent-filter>
</service>
+
+ <!-- TODO(b/176993670): remove if DpmWrapper goes away -->
+ <receiver android:name="com.android.bedstead.dpmwrapper.TestAppCallbacksReceiver"
+ android:exported="true">
+ </receiver>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
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 e239153..cc06725 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
@@ -113,7 +113,7 @@
protected int getTargetApiLevel() throws Exception {
final PackageManager pm = mContext.getPackageManager();
- final PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), /* flags =*/ 0);
+ final PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), /* flags= */ 0);
return pi.applicationInfo.targetSdkVersion;
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegationTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegationTest.java
index 27b29f0..5def362 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegationTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegationTest.java
@@ -22,6 +22,7 @@
import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_SELECTION;
import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP;
import static android.app.admin.DevicePolicyManager.DELEGATION_NETWORK_LOGGING;
+import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
import static android.app.admin.DevicePolicyManager.EXTRA_DELEGATION_SCOPES;
import static com.google.common.truth.Truth.assertThat;
@@ -188,7 +189,8 @@
}
public void testDeviceOwnerOrManagedPoOnlyDelegations() throws Exception {
- final String doOrManagedPoDelegations[] = {DELEGATION_NETWORK_LOGGING};
+ final String [] doOrManagedPoDelegations =
+ { DELEGATION_NETWORK_LOGGING, DELEGATION_SECURITY_LOGGING };
final boolean isDeviceOwner = mDevicePolicyManager.isDeviceOwnerApp(
mContext.getPackageName());
final boolean isManagedProfileOwner = mDevicePolicyManager.getProfileOwner() != null
@@ -214,6 +216,7 @@
DELEGATION_CERT_SELECTION));
if (mDevicePolicyManager.isDeviceOwnerApp(mContext.getPackageName())) {
exclusiveDelegations.add(DELEGATION_NETWORK_LOGGING);
+ exclusiveDelegations.add(DELEGATION_SECURITY_LOGGING);
}
for (String scope : exclusiveDelegations) {
testExclusiveDelegation(scope);
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 50cf4ba..3acce6e 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
@@ -186,6 +186,11 @@
mDevicePolicyManager.setUninstallBlocked(ADMIN_RECEIVER_COMPONENT, PACKAGE_NAME, false);
}
+ public void testSetEnterpriseNetworkPreferenceEnabledLogged() {
+ mDevicePolicyManager.setEnterpriseNetworkPreferenceEnabled(true);
+ mDevicePolicyManager.setEnterpriseNetworkPreferenceEnabled(false);
+ }
+
public void testDisallowAdjustVolumeMutedLogged() {
final boolean initialValue =
mDevicePolicyManager.isMasterVolumeMuted(ADMIN_RECEIVER_COMPONENT);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java
index 10971d7..aa195fa 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NetworkSlicingStatusTest.java
@@ -32,14 +32,14 @@
super.tearDown();
}
- public void testGetSetNetworkSlicingStatus() throws Exception {
+ public void testGetSetEnterpriseNetworkPreferenceStatus() throws Exception {
// Assert default status is true
- assertTrue(mDevicePolicyManager.isNetworkSlicingEnabled());
+ assertTrue(mDevicePolicyManager.isEnterpriseNetworkPreferenceEnabled());
- mDevicePolicyManager.setNetworkSlicingEnabled(false);
- assertFalse(mDevicePolicyManager.isNetworkSlicingEnabled());
+ mDevicePolicyManager.setEnterpriseNetworkPreferenceEnabled(false);
+ assertFalse(mDevicePolicyManager.isEnterpriseNetworkPreferenceEnabled());
- mDevicePolicyManager.setNetworkSlicingEnabled(true);
- assertTrue(mDevicePolicyManager.isNetworkSlicingEnabled());
+ mDevicePolicyManager.setEnterpriseNetworkPreferenceEnabled(true);
+ assertTrue(mDevicePolicyManager.isEnterpriseNetworkPreferenceEnabled());
}
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
index d639cb9..2ad3a78 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecondaryLockscreenTest.java
@@ -67,6 +67,10 @@
mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_SECURE_LOCK_SCREEN));
+ // TODO(b/182994391): Replace with more generic solution to override the supervision
+ // component.
+ mUiDevice.executeShellCommand("settings put global device_policy_constants "
+ + "use_test_admin_as_supervision_component=true");
mUiDevice.executeShellCommand("locksettings set-disabled false");
mUiDevice.executeShellCommand("locksettings set-pin 1234");
@@ -82,6 +86,7 @@
public void tearDown() throws Exception {
mDevicePolicyManager.setSecondaryLockscreenEnabled(ADMIN_RECEIVER_COMPONENT, false);
assertFalse(mDevicePolicyManager.isSecondaryLockscreenEnabled(Process.myUserHandle()));
+ mUiDevice.executeShellCommand("settings delete global device_policy_constants");
mUiDevice.executeShellCommand("locksettings clear --old 1234");
mUiDevice.executeShellCommand("locksettings set-disabled true");
}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
index 8c4510d..38c949d 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
@@ -61,6 +61,7 @@
import static com.android.cts.devicepolicy.TestCertificates.TEST_CA_SUBJECT;
import static com.google.common.collect.ImmutableList.of;
+import static com.google.common.truth.Truth.assertThat;
import android.app.admin.SecurityLog.SecurityEvent;
import android.content.Context;
@@ -102,6 +103,9 @@
private static final String PREF_NAME = "batchIds";
// system/core/liblog/event.logtags: 1006 liblog (dropped|1)
private static final int TAG_LIBLOG_DROPPED = 1006;
+ private static final String DELEGATE_APP_PKG = "com.android.cts.delegate";
+ private static final String DELEGATION_SECURITY_LOGGING = "delegation-security-logging";
+
// For brevity.
private static final Class<String> S = String.class;
@@ -218,7 +222,10 @@
public void testVerifyGeneratedLogs() throws Exception {
final List<SecurityEvent> events = getEvents();
verifyAutomaticEventsPresent(events);
- verifyKeystoreEventsPresent(events);
+
+ // STOPSHIP(b/183201685): re-enable when KeyStore2 logs these events.
+ // verifyKeystoreEventsPresent(events);
+
verifyKeyChainEventsPresent(events);
verifyAdminEventsPresent(events);
verifyAdbShellCommand(events); // Event generated from host side logic
@@ -541,6 +548,23 @@
}
}
+ public void testSetDelegateScope_delegationSecurityLogging() {
+ mDevicePolicyManager.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG,
+ Arrays.asList(DELEGATION_SECURITY_LOGGING));
+
+ assertThat(mDevicePolicyManager.getDelegatedScopes(
+ ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG)).contains(DELEGATION_SECURITY_LOGGING);
+ }
+
+ public void testSetDelegateScope_noDelegation() {
+ mDevicePolicyManager.setDelegatedScopes(ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG,
+ Arrays.asList());
+
+ assertThat(mDevicePolicyManager.getDelegatedScopes(
+ ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG))
+ .doesNotContain(DELEGATION_SECURITY_LOGGING);
+ }
+
private void generateKey(String keyAlias) throws Exception {
final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
generator.initialize(
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 41b3cd8..e4d0ff2 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
@@ -24,6 +24,8 @@
import android.util.Log;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Set;
public class SuspendPackageTest extends BaseDeviceAdminTest {
@@ -88,8 +90,25 @@
String[] notHandledPackages = setSuspendedPackages(/* suspend= */ true,
launcherPackage, dpcPackage);
// no package should be handled.
- assertWithMessage("not handled pacakges").that(notHandledPackages).asList()
+ assertWithMessage("not handled packages").that(notHandledPackages).asList()
.containsExactly(launcherPackage, dpcPackage);
+
+ Set<String> exemptApps = mDevicePolicyManager.getPolicyExemptApps();
+ if (exemptApps.isEmpty()) {
+ Log.v(TAG, "testSuspendNotSuspendablePackages(): no exempt apps");
+ return;
+ }
+
+ Log.v(TAG, "testSuspendNotSuspendablePackages(): testing exempt apps: " + exemptApps);
+ notHandledPackages = setSuspendedPackages(/* suspend= */ true, exemptApps);
+ assertWithMessage("exempt apps not suspended").that(notHandledPackages).asList()
+ .containsExactlyElementsIn(exemptApps);
+ }
+
+ private String[] setSuspendedPackages(boolean suspend, Collection<String> pkgs) {
+ String[] pkgsArray = new String[pkgs.size()];
+ pkgs.toArray(pkgsArray);
+ return setSuspendedPackages(suspend, pkgsArray);
}
private String[] setSuspendedPackages(boolean suspend, String... pkgs) {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/systemupdate/InstallUpdateTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/systemupdate/InstallUpdateTest.java
old mode 100644
new mode 100755
index 3a3a357..205218d
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/systemupdate/InstallUpdateTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/systemupdate/InstallUpdateTest.java
@@ -206,6 +206,7 @@
&& SystemClock.elapsedRealtime() <= startTime + BATTERY_STATE_CHANGE_TIMEOUT_MS) {
Thread.sleep(BATTERY_STATE_CHANGE_SLEEP_PER_CHECK_MS);
}
+ assertTrue("Battery state update timeout", isBatteryState(plugged, level));
}
private boolean isBatteryState(boolean plugged, int level) {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index ee8c274..7533d80 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -31,6 +31,8 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceOwnerProvisioningTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceOwnerProvisioningTest.java
deleted file mode 100644
index ee94320..0000000
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceOwnerProvisioningTest.java
+++ /dev/null
@@ -1,123 +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.deviceowner;
-
-import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED;
-import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static java.util.stream.Collectors.toList;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.util.Log;
-
-import com.android.compatibility.common.util.devicepolicy.provisioning.SilentProvisioningTestManager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class DeviceOwnerProvisioningTest extends BaseDeviceOwnerTest {
- private static final String TAG = "DeviceOwnerProvisioningTest";
-
- private List<String> mEnabledAppsBeforeTest;
- private PackageManager mPackageManager;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mPackageManager = mContext.getPackageManager();
- mEnabledAppsBeforeTest = getSystemPackageNameList();
-
- Log.d(TAG, "EnabledAppsBeforeTest: " + mEnabledAppsBeforeTest);
- }
-
- @Override
- protected void tearDown() throws Exception {
- enableUninstalledApp();
- super.tearDown();
- }
-
- public void testProvisionDeviceOwner() throws Exception {
- deviceOwnerProvision(getBaseProvisioningIntent());
- }
-
- public void testProvisionDeviceOwner_withAllSystemAppsEnabled() throws Exception {
- List<String> systemAppsBefore = getSystemPackageNameList();
-
- Intent intent = getBaseProvisioningIntent()
- .putExtra(EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED, true);
- deviceOwnerProvision(intent);
-
- List<String> systemAppsAfter = getSystemPackageNameList();
- assertTrue(systemAppsBefore.equals(systemAppsAfter));
- }
-
- private void enableUninstalledApp() {
- final List<String> currentEnabledApps = getSystemPackageNameList();
-
- final List<String> disabledApps = new ArrayList<String>(mEnabledAppsBeforeTest);
- disabledApps.removeAll(currentEnabledApps);
-
- for (String disabledSystemApp : disabledApps) {
- Log.i(TAG, "enabling app " + disabledSystemApp);
- mDevicePolicyManager.enableSystemApp(BasicAdminReceiver.getComponentName(mContext),
- disabledSystemApp);
- }
- }
-
- private Intent getBaseProvisioningIntent() {
- return new Intent(ACTION_PROVISION_MANAGED_DEVICE)
- .putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
- BasicAdminReceiver.getComponentName(mContext))
- .putExtra(DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION, true);
- }
-
- private void deviceOwnerProvision(Intent intent) throws Exception {
- SilentProvisioningTestManager provisioningManager =
- new SilentProvisioningTestManager(mContext);
- assertWithMessage("provisinioning with intent %s on user %s started", intent, mUserId)
- .that(provisioningManager.startProvisioningAndWait(intent)).isTrue();
- Log.i(TAG, "device owner provisioning successful for user " + mUserId);
- String pkg = mContext.getPackageName();
- assertWithMessage("%s is deviceOwner", pkg).that(mDevicePolicyManager.isDeviceOwnerApp(pkg))
- .isTrue();
- Log.i(TAG, "device owner app: " + pkg);
- assertWithMessage("Admin should be able to grant sensors permissions by default").that(
- mDevicePolicyManager.canAdminGrantSensorsPermissions()).isTrue();
- }
-
- private List<String> getPackageNameList() {
- return getPackageNameList(0 /* Default flags */);
- }
-
- private List<String> getSystemPackageNameList() {
- return getPackageNameList(MATCH_SYSTEM_ONLY);
- }
-
- private List<String> getPackageNameList(int flags) {
- return mPackageManager.getInstalledApplications(flags)
- .stream()
- .map((ApplicationInfo appInfo) -> appInfo.packageName)
- .sorted()
- .collect(toList());
- }
-}
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.java
index 7230023..b69e2df 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiNetworkConfigurationWithoutFineLocationPermissionTest.java
@@ -23,10 +23,13 @@
import android.content.pm.PackageManager;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
+import android.os.SystemClock;
+import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.WifiConfigCreator;
import java.util.List;
+import java.util.concurrent.TimeUnit;
public class WifiNetworkConfigurationWithoutFineLocationPermissionTest extends BaseDeviceOwnerTest {
private static final String TAG = "WifiNetworkConfigurationWithoutFineLocationPermissionTest";
@@ -35,6 +38,10 @@
private static final String NETWORK_SSID = "com.android.cts.abcdefghijklmnop";
private static final int INVALID_NETWORK_ID = -1;
+ // Time duration to allow before assuming that a WiFi operation failed and ceasing to wait.
+ private static final long UPDATE_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(5);
+ private static final long UPDATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(1);
+
private WifiManager mWifiManager;
private WifiConfigCreator mWifiConfigCreator;
@@ -44,6 +51,12 @@
mWifiConfigCreator = new WifiConfigCreator(getContext());
mWifiManager = getContext().getSystemService(WifiManager.class);
+ // WiFi is supposed to be a prerequisite of CTS but sometimes it's not enabled
+ // for some unknown reason. Check it here just in case.
+ if (!mWifiManager.isWifiEnabled()) {
+ SystemUtil.runShellCommand("svc wifi enable");
+ awaitWifiEnabled();
+ }
}
public void testAddAndRetrieveCallerConfiguredNetworks() throws Exception {
@@ -63,4 +76,16 @@
mWifiManager.removeNetwork(netId);
}
}
+
+ private void awaitWifiEnabled() {
+ for (int probes = 0; probes * UPDATE_INTERVAL_MS <= UPDATE_TIMEOUT_MS; probes++) {
+ if (probes != 0) {
+ SystemClock.sleep(UPDATE_INTERVAL_MS);
+ }
+ if (mWifiManager.isWifiEnabled()) {
+ return;
+ }
+ }
+ fail("Waited too long for wifi enabled");
+ }
}
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
index 045ba88..6fe23d2 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
@@ -28,7 +28,7 @@
* running in a managed profile.
*/
public class SimpleIntentReceiverActivity extends Activity {
- private static final String TAG = "SimpleIntentReceiverActivity";
+ private static final String TAG = SimpleIntentReceiverActivity.class.getSimpleName();
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -41,8 +41,14 @@
(DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
boolean inManagedProfile = dpm.isProfileOwnerApp("com.android.cts.managedprofile");
- Log.i(TAG, "activity " + className + " started on user " + getUserId()
- + ", is in managed profile: " + inManagedProfile);
+ try {
+ Log.i(TAG, "activity " + className + " started on user " + getUserId()
+ + ", is in managed profile: " + inManagedProfile);
+ } catch (NoSuchMethodError e) {
+ // TODO(b/183427655): figure out why it's failing...
+ Log.i(TAG, "activity " + className + ", is in managed profile: " + inManagedProfile
+ + " (could not infer user id: " + e + ")");
+ }
Intent result = new Intent();
result.putExtra("extra_receiver_class", className);
result.putExtra("extra_in_managed_profile", inManagedProfile);
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
index b55437c..935090f 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
@@ -32,7 +32,7 @@
public class IntentSenderActivity extends Activity {
- private static String TAG = "IntentSenderActivity";
+ private static final String TAG = IntentSenderActivity.class.getSimpleName();
private final SynchronousQueue<Result> mResult = new SynchronousQueue<>();
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/SuspendPackageTest.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/SuspendPackageTest.java
index 991bcb3..16d4f03 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/SuspendPackageTest.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/SuspendPackageTest.java
@@ -97,19 +97,22 @@
Intent intent = new Intent();
intent.setClassName(INTENT_RECEIVER_PKG, TARGET_ACTIVITY_NAME);
if (!temporarilySkipActivityLaunch()) {
- Log.d(TAG, "assertPackageSuspended(suspended=" + suspended
- + ", customDialog=" + customDialog + "): getting result for activity " + intent
- + " on user " + mContext.getUserId());
Intent result = mActivity.getResult(intent);
+ Log.d(TAG, "assertPackageSuspended(suspended=" + suspended
+ + ", customDialog=" + customDialog + "): result for activity "
+ + INTENT_RECEIVER_PKG + "/" + TARGET_ACTIVITY_NAME + " on user "
+ + mContext.getUserId() + ": " + result);
if (suspended) {
if (customDialog) {
dismissCustomDialog();
} else {
dismissPolicyTransparencyDialog();
}
- assertWithMessage("result for activitiy %s", intent).that(result).isNull();
+ assertWithMessage("result for activitiy %s while suspended", intent).that(result)
+ .isNull();
} else {
- assertWithMessage("result for activitiy %s", intent).that(result).isNotNull();
+ assertWithMessage("result for activitiy %s while NOT suspended", intent)
+ .that(result).isNotNull();
}
}
// No matter if it is suspended or not, we should be able to resolve the activity.
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 2c460a2..78cff70 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -25,6 +25,8 @@
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.CAMERA"/>
@@ -51,15 +53,6 @@
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
</intent-filter>
</receiver>
- <receiver android:name="com.android.cts.managedprofile.ProvisioningTest$ProvisioningAdminReceiver"
- android:permission="android.permission.BIND_DEVICE_ADMIN"
- android:exported="true">
- <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>
<receiver android:name="com.android.cts.managedprofile.PrimaryUserDeviceAdmin"
android:permission="android.permission.BIND_DEVICE_ADMIN"
android:exported="true">
@@ -161,18 +154,6 @@
</service>
<activity android:name="com.android.compatibility.common.util.devicepolicy.provisioning.StartProvisioningActivity"/>
- <activity android:name=".ProvisioningSuccessActivity"
- android:theme="@android:style/Theme.NoDisplay"
- android:exported="true">
- <intent-filter>
- <action android:name="android.app.action.PROVISIONING_SUCCESSFUL"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- </activity>
-
- <activity android:name=".WebViewActivity"
- android:process=":testProcess"/>
-
<activity android:name=".TimeoutActivity"
android:exported="true"/>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningSuccessActivity.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningSuccessActivity.java
deleted file mode 100644
index cfce578..0000000
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningSuccessActivity.java
+++ /dev/null
@@ -1,29 +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.managedprofile;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class ProvisioningSuccessActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ProvisioningTest.getSharedPreferences(this).edit()
- .putBoolean(ProvisioningTest.KEY_PROVISIONING_SUCCESSFUL_RECEIVED, true).commit();
- finish();
- }
-}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningTest.java
deleted file mode 100644
index 8cf156b..0000000
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ProvisioningTest.java
+++ /dev/null
@@ -1,182 +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.managedprofile;
-
-import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION;
-import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION;
-
-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.accounts.Account;
-import android.accounts.AccountManager;
-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.SharedPreferences;
-import android.os.PersistableBundle;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.devicepolicy.provisioning.SilentProvisioningTestManager;
-
-import org.junit.Before;
-import org.junit.Test;
-
-@SmallTest
-public class ProvisioningTest {
- private static final String TAG = ProvisioningTest.class.getSimpleName();
-
- private static final String SHARED_PREFERENCE_FILE_NAME = "shared-preferences-file-name";
-
- private static final PersistableBundle ADMIN_EXTRAS_BUNDLE = new PersistableBundle();
- private static final String ADMIN_EXTRAS_BUNDLE_KEY_1 = "KEY_1";
- private static final String ADMIN_EXTRAS_BUNDLE_VALUE_1 = "VALUE_1";
- static {
- ADMIN_EXTRAS_BUNDLE.putString(ADMIN_EXTRAS_BUNDLE_KEY_1, ADMIN_EXTRAS_BUNDLE_VALUE_1);
- }
-
- public static final String KEY_PROVISIONING_SUCCESSFUL_RECEIVED =
- "key-provisioning-successful-received";
-
- private static final ComponentName ADMIN_RECEIVER_COMPONENT = new ComponentName(
- ProvisioningAdminReceiver.class.getPackage().getName(),
- ProvisioningAdminReceiver.class.getName());
-
- public static class ProvisioningAdminReceiver extends DeviceAdminReceiver {
- @Override
- public void onProfileProvisioningComplete(Context context, Intent intent) {
- super.onProfileProvisioningComplete(context, intent);
- // Enabled profile
- getManager(context).setProfileName(ADMIN_RECEIVER_COMPONENT, "Managed Profile");
- getManager(context).setProfileEnabled(ADMIN_RECEIVER_COMPONENT);
- Log.i(TAG, "onProfileProvisioningComplete");
-
- saveBundle(context, intent.getParcelableExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE));
- }
- }
-
- private Context mContext;
- private DevicePolicyManager mDpm;
-
- @Before
- public void setUp() {
- mContext = InstrumentationRegistry.getTargetContext();
- mDpm = mContext.getSystemService(DevicePolicyManager.class);
- }
-
- @Test
- public void testIsManagedProfile() {
- assertTrue(mDpm.isManagedProfile(ADMIN_RECEIVER_COMPONENT));
- Log.i(TAG, "managed profile app: " + ADMIN_RECEIVER_COMPONENT.getPackageName());
- }
-
- @Test
- public void testProvisionManagedProfile() throws InterruptedException {
- provisionManagedProfile(createBaseProvisioningIntent());
- }
-
- @Test
- public void testProvisionManagedProfile_accountCopy() throws InterruptedException {
- provisionManagedProfile(createBaseProvisioningIntent()
- .putExtra(EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION, true));
- }
-
- @Test
- public void testVerifyAdminExtraBundle() {
- PersistableBundle bundle = loadBundle(mContext);
- assertNotNull(bundle);
- assertEquals(ADMIN_EXTRAS_BUNDLE_VALUE_1, bundle.getString(ADMIN_EXTRAS_BUNDLE_KEY_1));
- }
-
- @Test
- public void testVerifySuccessfulIntentWasReceived() {
- assertTrue(getSharedPreferences(mContext).getBoolean(KEY_PROVISIONING_SUCCESSFUL_RECEIVED,
- false));
- }
-
- @Test
- public void testAccountExist() {
- AccountManager am = AccountManager.get(mContext);
- for (Account account : am.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)) {
- if (AccountAuthenticator.TEST_ACCOUNT.equals(account)) {
- return;
- }
- }
- fail("can't find migrated account");
- }
-
- @Test
- public void testAccountNotExist() {
- AccountManager am = AccountManager.get(mContext);
- assertTrue("test account still exists after account migration",
- am.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE).length == 0);
- }
-
- private Intent createBaseProvisioningIntent() {
- return new Intent(ACTION_PROVISION_MANAGED_PROFILE)
- .putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, ADMIN_RECEIVER_COMPONENT)
- .putExtra(EXTRA_PROVISIONING_SKIP_ENCRYPTION, true)
- .putExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, ADMIN_EXTRAS_BUNDLE)
- .putExtra(EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE, addAndGetTestAccount());
- }
-
- private void provisionManagedProfile(Intent intent) throws InterruptedException {
- SilentProvisioningTestManager provisioningManager = new SilentProvisioningTestManager(mContext);
- assertTrue(provisioningManager.startProvisioningAndWait(intent));
- Log.i(TAG, "managed profile provisioning successful");
- }
-
- private Account addAndGetTestAccount() {
- Account account = AccountAuthenticator.TEST_ACCOUNT;
- AccountManager.get(mContext).addAccountExplicitly(account, null, null);
- return account;
- }
-
- private static void saveBundle(Context context, PersistableBundle bundle) {
- if (bundle == null) {
- Log.e(TAG, "null saveBundle");
- return;
- }
-
- getSharedPreferences(context).edit()
- .putString(ADMIN_EXTRAS_BUNDLE_KEY_1, bundle.getString(ADMIN_EXTRAS_BUNDLE_KEY_1))
- .commit();
- }
-
- private static PersistableBundle loadBundle(Context context) {
- SharedPreferences pref = getSharedPreferences(context);
- PersistableBundle bundle = new PersistableBundle();
- bundle.putString(ADMIN_EXTRAS_BUNDLE_KEY_1,
- pref.getString(ADMIN_EXTRAS_BUNDLE_KEY_1, null));
- return bundle;
- }
-
- public static SharedPreferences getSharedPreferences(Context context) {
- return context.getSharedPreferences(SHARED_PREFERENCE_FILE_NAME, 0);
- }
-
-}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java
index bba2e56..a508201 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WifiTest.java
@@ -53,30 +53,17 @@
// Shared WifiManager instance.
private WifiManager mWifiManager;
- // Original setting of WifiManager.isWifiEnabled() before setup.
- private boolean mWifiEnabled;
-
@Override
public void setUp() throws Exception {
super.setUp();
mWifiConfigCreator = new WifiConfigCreator(getContext());
mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
- mWifiEnabled = mWifiManager.isWifiEnabled();
- if (!mWifiEnabled) {
+ if (!mWifiManager.isWifiEnabled()) {
SystemUtil.runShellCommand("svc wifi enable");
awaitWifiEnabledState(true);
}
}
- @Override
- public void tearDown() throws Exception {
- if (!mWifiEnabled) {
- SystemUtil.runShellCommand("svc wifi disable");
- awaitWifiEnabledState(false);
- }
- super.tearDown();
- }
-
/**
* Add a network through the WifiManager API. Verifies that the network was actually added.
*
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java
index 1752d0f..0c01de9 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java
@@ -52,7 +52,7 @@
if (isHeadlessSystemUserMode()) {
affiliateUsers(DEVICE_OWNER_PKG, mDeviceOwnerUserId, mPrimaryUserId);
- grantDpmWrapperPermissions(mPrimaryUserId);
+ grantDpmWrapperPermissions(DEVICE_OWNER_PKG, mPrimaryUserId);
}
// Enable the notification listener
@@ -60,19 +60,6 @@
+ "deviceowner/com.android.cts.deviceowner.NotificationListener");
}
- protected void grantDpmWrapperPermissions(int userId) throws Exception {
- // TODO(b/176993670): INTERACT_ACROSS_USERS is needed by DevicePolicyManagerWrapper to
- // get the current user; the permission is available on mDeviceOwnerUserId because it
- // was installed with -g, but not on mPrimaryUserId as the app is intalled by code
- // (DPMS.manageUserUnchecked(), which don't grant it (as this is a privileged permission
- // that's not available to 3rd party apps). If we get rid of DevicePolicyManagerWrapper,
- // we won't need to grant it anymore.
- CLog.i("Granting INTERACT_ACROSS_USERS to DO %s on user %d as it will need to send ordered "
- + "broadcasts to user 0", DEVICE_OWNER_PKG, userId);
- executeShellCommand("pm grant --user %d %s android.permission.INTERACT_ACROSS_USERS",
- userId, DEVICE_OWNER_PKG);
- }
-
@Override
public void tearDown() throws Exception {
if (mDeviceOwnerSet && !removeAdmin(DEVICE_OWNER_COMPONENT, mDeviceOwnerUserId)) {
@@ -84,14 +71,6 @@
super.tearDown();
}
- void affiliateUsers(String deviceAdminPkg, int userId1, int userId2) throws Exception {
- CLog.d("Affiliating users %d and %d on admin package %s", userId1, userId2, deviceAdminPkg);
- runDeviceTestsAsUser(
- deviceAdminPkg, ".AffiliationTest", "testSetAffiliationId1", userId1);
- runDeviceTestsAsUser(
- deviceAdminPkg, ".AffiliationTest", "testSetAffiliationId1", userId2);
- }
-
protected final void executeDeviceOwnerTest(String testClassName) throws Exception {
String testClass = DEVICE_OWNER_PKG + "." + testClassName;
runDeviceTestsAsUser(DEVICE_OWNER_PKG, testClass, mPrimaryUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index d9d230c..92c4673 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -16,8 +16,6 @@
package com.android.cts.devicepolicy;
-import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -870,6 +868,16 @@
assertFalse(setDeviceOwner(componentName, userId, /* expectFailure =*/ true));
}
+
+ protected void affiliateUsers(String deviceAdminPkg, int userId1, int userId2)
+ throws Exception {
+ CLog.d("Affiliating users %d and %d on admin package %s", userId1, userId2, deviceAdminPkg);
+ runDeviceTestsAsUser(
+ deviceAdminPkg, ".AffiliationTest", "testSetAffiliationId1", userId1);
+ runDeviceTestsAsUser(
+ deviceAdminPkg, ".AffiliationTest", "testSetAffiliationId1", userId2);
+ }
+
protected String getSettings(String namespace, String name, int userId)
throws DeviceNotAvailableException {
String command = "settings --user " + userId + " get " + namespace + " " + name;
@@ -1178,6 +1186,19 @@
return "true".equalsIgnoreCase(result);
}
+ protected void grantDpmWrapperPermissions(String deviceAdminPkg, int userId) throws Exception {
+ // TODO(b/176993670): INTERACT_ACROSS_USERS is needed by DevicePolicyManagerWrapper to
+ // get the current user; the permission is available on mDeviceOwnerUserId because it
+ // was installed with -g, but not on mPrimaryUserId as the app is intalled by code
+ // (DPMS.manageUserUnchecked(), which don't grant it (as this is a privileged permission
+ // that's not available to 3rd party apps). If we get rid of DevicePolicyManagerWrapper,
+ // we won't need to grant it anymore.
+ CLog.i("Granting INTERACT_ACROSS_USERS to DO %s on user %d as it will need to send ordered "
+ + "broadcasts to user 0", deviceAdminPkg, userId);
+ executeShellCommand("pm grant --user %d %s android.permission.INTERACT_ACROSS_USERS",
+ userId, deviceAdminPkg);
+ }
+
boolean isTv() throws DeviceNotAvailableException {
return hasDeviceFeature(FEATURE_LEANBACK);
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 902438f..70f725c 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -1877,9 +1877,6 @@
public void testAddNetworkWithKeychainKey_granted() throws Exception {
assumeHasWifiFeature();
- // STOPSHIP(b/160457441): remove when KS2 is the only one.
- assumeTrue(getBooleanSystemProperty("persist.android.security.keystore2.enable", false));
-
executeDeviceTestMethod(".WifiTest", "testAddNetworkWithKeychainKey_granted");
}
@@ -1887,9 +1884,6 @@
public void testAddNetworkSuggestionWithKeychainKey_granted() throws Exception {
assumeHasWifiFeature();
- // STOPSHIP(b/160457441): remove when KS2 is the only one.
- assumeTrue(getBooleanSystemProperty("persist.android.security.keystore2.enable", false));
-
executeDeviceTestMethod(".WifiTest", "testAddNetworkSuggestionWithKeychainKey_granted");
}
@@ -1897,9 +1891,6 @@
public void testAddNetworkSuggestionWithKeychainKey_notGranted() throws Exception {
assumeHasWifiFeature();
- // STOPSHIP(b/160457441): remove when KS2 is the only one.
- assumeTrue(getBooleanSystemProperty("persist.android.security.keystore2.enable", false));
-
executeDeviceTestMethod(".WifiTest", "testAddNetworkSuggestionWithKeychainKey_notGranted");
}
@@ -1907,9 +1898,6 @@
public void testAddNetworkWithKeychainKey_notGranted() throws Exception {
assumeHasWifiFeature();
- // STOPSHIP(b/160457441): remove when KS2 is the only one.
- assumeTrue(getBooleanSystemProperty("persist.android.security.keystore2.enable", false));
-
executeDeviceTestMethod(".WifiTest", "testAddNetworkWithKeychainKey_notGranted");
}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
index 57cb09a..e2b701d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
@@ -118,7 +118,7 @@
*/
@FlakyTest
@Test
- @Ignore
+ @Ignore("b/183395856 Migrate to a device side test.")
public void testCannotAddManagedProfileViaManagedProvisioning()
throws Exception {
int profileUserId = provisionCorpOwnedManagedProfile();
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 4b8accd..3de07bb 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -568,11 +568,6 @@
executeDeviceOwnerTest("SetLocationEnabledTest");
}
- @Test
- public void testDeviceOwnerProvisioning() throws Exception {
- executeDeviceOwnerTest("DeviceOwnerProvisioningTest");
- }
-
/**
* Only allow provisioning flow to be disabled if Android TV device
*/
@@ -942,7 +937,7 @@
installAppAsUser(DEVICE_OWNER_APK, userId);
setProfileOwnerOrFail(DEVICE_OWNER_COMPONENT, userId);
} else {
- grantDpmWrapperPermissions(userId);
+ grantDpmWrapperPermissions(DEVICE_OWNER_APK, userId);
}
wakeupAndDismissKeyguard();
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
index b57b3fb..86a90d2 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
@@ -21,6 +21,7 @@
import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
+import org.junit.Ignore;
import org.junit.Test;
/**
@@ -59,6 +60,7 @@
@FlakyTest
@Test
+ @Ignore("b/183395856 Figure out if it should be removed or converted to a device side test.")
public void testEXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME() throws Exception {
runDeviceTestsAsUser(SINGLE_ADMIN_PKG, ".ProvisioningSingleAdminTest",
"testManagedProfileProvisioning", mPrimaryUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
deleted file mode 100644
index ee6c94a..0000000
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
+++ /dev/null
@@ -1,126 +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.devicepolicy;
-
-import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
-
-import android.platform.test.annotations.FlakyTest;
-
-import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
-
-import org.junit.Test;
-
-// We need multi user to be supported in order to create a profile of the user owner.
-@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
-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";
-
- private int mProfileUserId;
- private int mParentUserId;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- removeTestUsers();
- mParentUserId = mPrimaryUserId;
- installAppAsUser(MANAGED_PROFILE_APK, mParentUserId);
- mProfileUserId = 0;
- }
-
- @Override
- public void tearDown() throws Exception {
- if (mProfileUserId != 0) {
- removeUser(mProfileUserId);
- }
- // Remove the test app account: also done by uninstallPackage
- getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
-
- super.tearDown();
- }
- @FlakyTest(bugId = 141747631)
- @Test
- public void testManagedProfileProvisioning() throws Exception {
- provisionManagedProfile();
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
- "testIsManagedProfile", mProfileUserId);
- }
-
- @FlakyTest(bugId = 127275983)
- @Test
- public void testEXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE() throws Exception {
- provisionManagedProfile();
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
- "testVerifyAdminExtraBundle", mProfileUserId);
- }
-
- @FlakyTest(bugId = 141747631)
- @Test
- public void testVerifySuccessfulIntentWasReceived() throws Exception {
- provisionManagedProfile();
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
- "testVerifySuccessfulIntentWasReceived", mProfileUserId);
- }
-
- @FlakyTest(bugId = 141747631)
- @Test
- public void testAccountMigration() throws Exception {
- provisionManagedProfile();
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
- "testAccountExist", mProfileUserId);
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
- "testAccountNotExist", mParentUserId);
- }
-
- @FlakyTest(bugId = 141747631)
- @Test
- public void testAccountCopy() throws Exception {
- provisionManagedProfile_accountCopy();
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
- "testAccountExist", mProfileUserId);
-
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
- "testAccountExist", mParentUserId);
- }
-
- @FlakyTest(bugId = 141747631)
- @Test
- public void testWebview() throws Exception {
- // We start an activity containing WebView in another process and run provisioning to
- // test that the process is not killed.
- startActivityAsUser(mParentUserId, MANAGED_PROFILE_PKG, ".WebViewActivity");
- provisionManagedProfile();
- }
-
- private void provisionManagedProfile() throws Exception {
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
- "testProvisionManagedProfile", mParentUserId);
- mProfileUserId = getFirstManagedProfileUserId();
- }
-
- private void provisionManagedProfile_accountCopy() throws Exception {
- runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ProvisioningTest",
- "testProvisionManagedProfile_accountCopy", mParentUserId);
- mProfileUserId = getFirstManagedProfileUserId();
- }
-}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index a71bc1e..5963459 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -68,6 +68,7 @@
getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
fail("Failed to set device owner on user " + mDeviceOwnerUserId);
}
+ grantDpmWrapperPermissions(DEVICE_ADMIN_PKG, mUserId);
}
@Override
@@ -284,7 +285,6 @@
.build());
}
- @FlakyTest(bugId = 137093665)
@Test
public void testSecurityLoggingWithSingleUser() throws Exception {
// Backup stay awake setting because testGenerateLogs() will turn it off.
@@ -362,6 +362,61 @@
}
@Test
+ public void testSecurityLoggingDelegate() throws Exception {
+ installAppAsUser(DELEGATE_APP_APK, mUserId);
+ try {
+ // Test that the delegate cannot access the logs already
+ runDeviceTestsAsUser(DELEGATE_APP_PKG, ".SecurityLoggingDelegateTest",
+ "testCannotAccessApis", mUserId);
+
+ // Set security logging delegate
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+ "testSetDelegateScope_delegationSecurityLogging", mUserId);
+
+ runSecurityLoggingTests(DELEGATE_APP_PKG,
+ ".SecurityLoggingDelegateTest");
+ } finally {
+ // Remove security logging delegate
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+ "testSetDelegateScope_noDelegation", mUserId);
+ }
+ }
+
+ private void runSecurityLoggingTests(String packageName, String testClassName)
+ throws Exception {
+ // Backup stay awake setting because testGenerateLogs() will turn it off.
+ final String stayAwake = getDevice().getSetting("global", "stay_on_while_plugged_in");
+ try {
+ // Turn logging on.
+ runDeviceTestsAsUser(packageName, testClassName,
+ "testEnablingSecurityLogging", mUserId);
+ // Reboot to ensure ro.device_owner is set to true in logd and logging is on.
+ rebootAndWaitUntilReady();
+ waitForUserUnlock(mUserId);
+
+ // Generate various types of events on device side and check that they are logged.
+ runDeviceTestsAsUser(packageName, testClassName,
+ "testGenerateLogs", mUserId);
+ getDevice().executeShellCommand("whoami"); // Generate adb command securty event
+ getDevice().executeShellCommand("dpm force-security-logs");
+ runDeviceTestsAsUser(packageName, testClassName,
+ "testVerifyGeneratedLogs", mUserId);
+
+ // Immediately attempting to fetch events again should fail.
+ runDeviceTestsAsUser(packageName, testClassName,
+ "testSecurityLoggingRetrievalRateLimited", mUserId);
+ } finally {
+ // Turn logging off.
+ runDeviceTestsAsUser(packageName, testClassName,
+ "testDisablingSecurityLogging", mUserId);
+ // Restore stay awake setting.
+ if (stayAwake != null) {
+ getDevice().setSetting("global", "stay_on_while_plugged_in", stayAwake);
+ }
+ }
+ }
+
+ @Test
public void testLocationPermissionGrantNotifies() throws Exception {
installAppPermissionAppAsUser();
configureNotificationListener();
@@ -370,7 +425,6 @@
}
@Override
- @Ignore("b/158735247")
@Test
public void testAdminControlOverSensorPermissionGrantsDefault() throws Exception {
// In Device Owner mode, by default, admin should be able to grant sensors-related
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 4924f00..fa72b93 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -20,10 +20,13 @@
import android.platform.test.annotations.FlakyTest;
import android.platform.test.annotations.LargeTest;
+import android.stats.devicepolicy.EventId;
import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
import com.android.cts.devicepolicy.annotations.LockSettingsTest;
import com.android.cts.devicepolicy.annotations.PermissionsTest;
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier;
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
import com.android.tradefed.device.DeviceNotAvailableException;
import org.junit.Test;
@@ -135,8 +138,24 @@
}
@Test
- public void testSetGetNetworkSlicingStatus() throws Exception {
- executeDeviceTestMethod(".NetworkSlicingStatusTest", "testGetSetNetworkSlicingStatus");
+ public void testSetGetEnterpriseNetworkPreferenceStatus() throws Exception {
+ executeDeviceTestMethod(".testSetGetEnterpriseNetworkPreferenceStatus",
+ "testGetSetEnterpriseNetworkPreferenceStatus");
+ }
+
+ @Test
+ public void testSetEnterpriseNetworkPreferenceStatusLogged() throws Exception {
+ DevicePolicyEventLogVerifier.assertMetricsLogged(getDevice(), () -> {
+ executeDeviceTestMethod(".DevicePolicyLoggingTest",
+ "testSetEnterpriseNetworkPreferenceEnabledLogged");
+ }, new DevicePolicyEventWrapper.Builder(
+ EventId.SET_ENTERPRISE_NETWORK_PREFERENCE_ENABLED_VALUE)
+ .setBoolean(true)
+ .build(),
+ new DevicePolicyEventWrapper.Builder(
+ EventId.SET_ENTERPRISE_NETWORK_PREFERENCE_ENABLED_VALUE)
+ .setBoolean(false)
+ .build());
}
/** VPN tests don't require physical device for managed profile, thus overriding. */
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
index a05eab1..556f6ac 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
@@ -226,33 +226,58 @@
.build());
}
- @FlakyTest(bugId = 137093665)
@Test
public void testSecurityLogging() throws Exception {
+ installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+ testSecurityLoggingOnWorkProfile(DEVICE_ADMIN_PKG, ".SecurityLoggingTest");
+ }
+
+ @Test
+ public void testSecurityLoggingDelegate() throws Exception {
+ installAppAsUser(DELEGATE_APP_APK, mUserId);
+ installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
+ try {
+ runDeviceTestsAsUser(DELEGATE_APP_PKG, ".WorkProfileSecurityLoggingDelegateTest",
+ "testCannotAccessApis", mUserId);
+ // Set security logging delegate
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+ "testSetDelegateScope_delegationSecurityLogging", mUserId);
+
+ testSecurityLoggingOnWorkProfile(DELEGATE_APP_PKG,
+ ".WorkProfileSecurityLoggingDelegateTest");
+ } finally {
+ // Remove security logging delegate
+ runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+ "testSetDelegateScope_noDelegation", mUserId);
+ }
+ }
+
+ private void testSecurityLoggingOnWorkProfile(String packageName, String testClassName)
+ throws Exception {
// Backup stay awake setting because testGenerateLogs() will turn it off.
final String stayAwake = getDevice().getSetting("global", "stay_on_while_plugged_in");
try {
// Turn logging on.
- runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+ runDeviceTestsAsUser(packageName, testClassName,
"testEnablingSecurityLogging", mUserId);
// Reboot to ensure ro.device_owner is set to true in logd and logging is on.
rebootAndWaitUntilReady();
waitForUserUnlock(mUserId);
// Generate various types of events on device side and check that they are logged.
- runDeviceTestsAsUser(DEVICE_ADMIN_PKG,".SecurityLoggingTest",
+ runDeviceTestsAsUser(packageName, testClassName,
"testGenerateLogs", mUserId);
getDevice().executeShellCommand("whoami"); // Generate adb command securty event
getDevice().executeShellCommand("dpm force-security-logs");
- runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+ runDeviceTestsAsUser(packageName, testClassName,
"testVerifyGeneratedLogs", mUserId);
// Immediately attempting to fetch events again should fail.
- runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+ runDeviceTestsAsUser(packageName, testClassName,
"testSecurityLoggingRetrievalRateLimited", mUserId);
} finally {
// Turn logging off.
- runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".SecurityLoggingTest",
+ runDeviceTestsAsUser(packageName, testClassName,
"testDisablingSecurityLogging", mUserId);
// Restore stay awake setting.
if (stayAwake != null) {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
index d4c3c14..4ca9e8d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
@@ -15,14 +15,19 @@
*/
package com.android.cts.devicepolicy;
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
+
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
+
import org.junit.Test;
/**
* Host side tests for profile owner. Run the CtsProfileOwnerApp device side test.
*/
+@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
public class ProfileOwnerTest extends BaseDevicePolicyTest {
private static final String PROFILE_OWNER_PKG = "com.android.cts.profileowner";
private static final String PROFILE_OWNER_APK = "CtsProfileOwnerApp.apk";
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
index 5921d2e..301e1e4 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
@@ -15,8 +15,11 @@
*/
package com.android.cts.devicepolicy;
+import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
+
import static org.junit.Assert.assertTrue;
+import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
import com.android.tradefed.device.DeviceNotAvailableException;
import org.junit.Test;
@@ -49,7 +52,6 @@
super.setUp();
mRemoveOwnerInTearDown = false;
- mDeviceOwnerUserId = mPrimaryUserId;
}
@Override
@@ -120,6 +122,7 @@
}
// Checks restrictions for managed profile.
+ @RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
@Test
public void testUserRestrictions_managedProfileOwnerOnly() throws Exception {
assumeCanCreateOneManagedUser();
@@ -146,9 +149,15 @@
assumeSupportsMultiUser();
setDo();
- // Create another user and set PO.
- final int secondaryUserId = createUserAndWaitStart();
- setPoAsUser(secondaryUserId);
+ final int secondaryUserId;
+ if (!isHeadlessSystemUserMode()) {
+ // Create another user and set PO.
+ secondaryUserId = createUserAndWaitStart();
+ setPoAsUser(secondaryUserId);
+ } else {
+ // In headless system user mode, PO is set on primary user when DO is set
+ secondaryUserId = mPrimaryUserId;
+ }
// Ensure that UserManager differentiates its own restrictions from DO restrictions.
runTests("userrestrictions.DeviceOwnerUserRestrictionsTest",
@@ -210,10 +219,15 @@
public void testUserRestrictions_profileGlobalRestrictionsAsDo() throws Exception {
assumeSupportsMultiUser();
setDo();
-
- // Create another user with PO.
- final int secondaryUserId = createUserAndWaitStart();
- setPoAsUser(secondaryUserId);
+ final int secondaryUserId;
+ if (!isHeadlessSystemUserMode()) {
+ // Create another user and set PO.
+ secondaryUserId = createUserAndWaitStart();
+ setPoAsUser(secondaryUserId);
+ } else {
+ // In headless system user mode, PO is set on primary user when DO is set.
+ secondaryUserId = mPrimaryUserId;
+ }
final int[] usersToCheck = {mDeviceOwnerUserId, secondaryUserId};
@@ -225,6 +239,7 @@
* Managed profile owner sets profile global restrictions (only ENSURE_VERIFY_APPS), should
* affect all users.
*/
+ @RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
@Test
public void testUserRestrictions_ProfileGlobalRestrictionsAsPo() throws Exception {
assumeCanCreateOneManagedUser();
@@ -265,6 +280,11 @@
setDeviceOwner(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
mDeviceOwnerUserId, /*expectFailure*/ false));
mRemoveOwnerInTearDown = true;
+
+ if (isHeadlessSystemUserMode()) {
+ affiliateUsers(DEVICE_ADMIN_PKG, mDeviceOwnerUserId, mPrimaryUserId);
+ grantDpmWrapperPermissions(DEVICE_ADMIN_PKG, mPrimaryUserId);
+ }
}
/**
diff --git a/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java
index 40aafeb..3f97f1d 100644
--- a/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java
+++ b/hostsidetests/graphics/framerateoverride/app/src/com/android/cts/graphics/framerateoverride/FrameRateOverrideTest.java
@@ -115,7 +115,7 @@
// resolution (for example, a 120Hz mode when the device also supports a 60Hz mode).
private List<Display.Mode> getModesToTest() {
List<Display.Mode> modesToTest = new ArrayList<>();
- if (!SurfaceFlingerProperties.enable_frame_rate_override().orElse(true)) {
+ if (!SurfaceFlingerProperties.enable_frame_rate_override().orElse(false)) {
return modesToTest;
}
Display.Mode[] modes = mActivityRule.getActivity().getDisplay().getSupportedModes();
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
index 39cadcb..683dc51 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
@@ -352,10 +352,10 @@
while ((endTime - startTime <= timeoutMillis)) {
if (inputConsole.ready()) {
String line = inputConsole.readLine();
- if (line != null && line.toLowerCase().contains(expectedMessage)) {
+ if (line != null && line.toLowerCase().contains(expectedMessage.toLowerCase())) {
CLog.v("Found " + expectedMessage + " in " + line);
return true;
- } else if (line.toLowerCase().contains(CEC_PORT_BUSY)) {
+ } else if (line.toLowerCase().contains(CEC_PORT_BUSY.toLowerCase())) {
throw new CecPortBusyException();
}
}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java
index cb11d31..fd0f33c 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecStartupTest.java
@@ -18,8 +18,6 @@
import static com.google.common.truth.Truth.assertWithMessage;
-import static org.junit.Assume.assumeTrue;
-
import android.hdmicec.cts.BaseHdmiCecCtsTest;
import android.hdmicec.cts.CecOperand;
import android.hdmicec.cts.HdmiCecConstants;
@@ -27,20 +25,19 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.google.common.collect.ImmutableList;
-
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.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
- * HDMI CEC tests verifying CEC messages sent after startup (CEC 2.0 CTS Section 7.5)
+ * HDMI CEC tests verifying CEC messages sent after startup
*/
@RunWith(DeviceJUnit4ClassRunner.class)
public final class HdmiCecStartupTest extends BaseHdmiCecCtsTest {
@@ -53,19 +50,22 @@
.around(hdmiCecClient);
/**
+ * CEC 1.4
+ *
* Tests that the device sends all the messages that should be sent on startup. It also ensures
* that only the device only sends messages which are allowed by the spec.
*/
@Test
- public void cectVerifyStartupMessages() throws Exception {
+ public void cectVerifyStartupMessages_Cec14b() throws Exception {
ITestDevice device = getDevice();
List<CecOperand> expectedMessages = Collections.singletonList(
CecOperand.REPORT_PHYSICAL_ADDRESS);
- List<CecOperand> allowedMessages = Arrays.asList(CecOperand.VENDOR_COMMAND, CecOperand.GIVE_DEVICE_VENDOR_ID,
- CecOperand.SET_OSD_NAME, CecOperand.GIVE_OSD_NAME, CecOperand.CEC_VERSION,
- CecOperand.DEVICE_VENDOR_ID, CecOperand.GIVE_POWER_STATUS,
- CecOperand.GET_MENU_LANGUAGE);
+ List<CecOperand> allowedMessages = new ArrayList<>(
+ Arrays.asList(CecOperand.VENDOR_COMMAND, CecOperand.GIVE_DEVICE_VENDOR_ID,
+ CecOperand.SET_OSD_NAME, CecOperand.GIVE_OSD_NAME, CecOperand.CEC_VERSION,
+ CecOperand.DEVICE_VENDOR_ID, CecOperand.GIVE_POWER_STATUS,
+ CecOperand.GET_MENU_LANGUAGE));
allowedMessages.addAll(expectedMessages);
device.executeShellCommand("reboot");
@@ -75,8 +75,8 @@
hdmiCecClient.getAllMessages(mDutLogicalAddress, 20);
List<CecOperand> notPermittedMessages = messagesReceived.stream()
- .filter(message -> !expectedMessages.contains(message))
.filter(message -> !allowedMessages.contains(message))
+ .filter(message -> !expectedMessages.contains(message))
.collect(Collectors.toList());
List<CecOperand> requiredMessages = messagesReceived.stream()
@@ -87,10 +87,8 @@
notPermittedMessages).isEmpty();
assertWithMessage("Some necessary messages are missing").that(requiredMessages).hasSize(
expectedMessages.size());
- assertWithMessage("Expected <Report Features> first").that(
- requiredMessages.get(0)).isEqualTo(CecOperand.REPORT_FEATURES);
- assertWithMessage("Expected <Report Physical Address> last").that(
- requiredMessages.get(1)).isEqualTo(CecOperand.REPORT_PHYSICAL_ADDRESS);
+ assertWithMessage("Expected <Report Physical Address>").that(
+ requiredMessages.get(0)).isEqualTo(CecOperand.REPORT_PHYSICAL_ADDRESS);
}
/**
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java
index d9a3a25..44693dd 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java
@@ -173,13 +173,13 @@
"Caught "
+ e.getClass().getSimpleName()
+ ". "
- + "Could not get adapter mapping.");
+ + "Could not get adapter mapping.", e);
} catch (Exception generic) {
throw new TargetSetupError(
"Caught an exception with message '"
+ generic.getMessage()
+ "'. "
- + "Could not get adapter mapping.");
+ + "Could not get adapter mapping.", generic);
}
throw new TargetSetupError("Device not connected to any adapter!");
}
diff --git a/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
index 575233a..e8cf750 100644
--- a/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
+++ b/hostsidetests/incident/apps/batterystatsapp/AndroidManifest.xml
@@ -21,6 +21,8 @@
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
index 3c807fa..27f2c76 100644
--- a/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
@@ -19,6 +19,7 @@
import android.app.job.StopReasonEnum;
import android.net.NetworkCapabilitiesProto;
import android.net.NetworkRequestProto;
+import android.net.Transport;
import com.android.server.job.ConstantsProto;
import com.android.server.job.ConstraintEnum;
import com.android.server.job.DataSetProto;
@@ -228,8 +229,8 @@
private static void testNetworkCapabilitesProto(NetworkCapabilitiesProto nc) throws Exception {
assertNotNull(nc);
- for (NetworkCapabilitiesProto.Transport t : nc.getTransportsList()) {
- assertTrue(NetworkCapabilitiesProto.Transport.getDescriptor().getValues()
+ for (Transport t : nc.getTransportsList()) {
+ assertTrue(Transport.getDescriptor().getValues()
.contains(t.getValueDescriptor()));
}
for (NetworkCapabilitiesProto.NetCapability c : nc.getCapabilitiesList()) {
diff --git a/hostsidetests/media/app/MediaMetricsTest/Android.bp b/hostsidetests/media/app/MediaMetricsTest/Android.bp
index 942e070..7ac3324 100644
--- a/hostsidetests/media/app/MediaMetricsTest/Android.bp
+++ b/hostsidetests/media/app/MediaMetricsTest/Android.bp
@@ -28,6 +28,7 @@
],
static_libs: [
"androidx.test.rules",
+ "truth-prebuilt",
],
sdk_version: "test_current",
min_sdk_version: "30",
diff --git a/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java b/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java
index 8416e58..a252fa7 100644
--- a/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java
+++ b/hostsidetests/media/app/MediaMetricsTest/src/android/media/metrics/cts/MediaMetricsAtomHostSideTests.java
@@ -16,14 +16,19 @@
package android.media.metrics.cts;
+import static com.google.common.truth.Truth.assertThat;
+
import android.content.Context;
+import android.media.metrics.LogSessionId;
import android.media.metrics.MediaMetricsManager;
import android.media.metrics.NetworkEvent;
import android.media.metrics.PlaybackErrorEvent;
import android.media.metrics.PlaybackMetrics;
import android.media.metrics.PlaybackSession;
import android.media.metrics.PlaybackStateEvent;
+import android.media.metrics.RecordingSession;
import android.media.metrics.TrackChangeEvent;
+import android.os.Bundle;
import androidx.test.InstrumentationRegistry;
@@ -40,6 +45,7 @@
new PlaybackStateEvent.Builder()
.setTimeSinceCreatedMillis(1763L)
.setState(PlaybackStateEvent.STATE_JOINING_FOREGROUND)
+ .setMetricsBundle(new Bundle())
.build();
s.reportPlaybackStateEvent(e);
}
@@ -52,9 +58,10 @@
PlaybackErrorEvent e =
new PlaybackErrorEvent.Builder()
.setTimeSinceCreatedMillis(17630000L)
- .setErrorCode(PlaybackErrorEvent.ERROR_CODE_RUNTIME)
+ .setErrorCode(PlaybackErrorEvent.ERROR_RUNTIME)
.setSubErrorCode(378)
.setException(new Exception("test exception"))
+ .setMetricsBundle(new Bundle())
.build();
s.reportPlaybackErrorEvent(e);
}
@@ -88,6 +95,7 @@
new NetworkEvent.Builder()
.setTimeSinceCreatedMillis(3032L)
.setNetworkType(NetworkEvent.NETWORK_TYPE_WIFI)
+ .setMetricsBundle(new Bundle())
.build();
s.reportNetworkEvent(e);
}
@@ -113,7 +121,32 @@
.setNetworkBytesRead(102400)
.setLocalBytesRead(2000)
.setNetworkTransferDurationMillis(6000)
+ .setDrmSessionId(new byte[] {2, 3, 3, 10})
+ .setMetricsBundle(new Bundle())
.build();
s.reportPlaybackMetrics(e);
}
+
+ @Test
+ public void testSessionId() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+ PlaybackSession s = manager.createPlaybackSession();
+
+ LogSessionId idObj = s.getSessionId();
+ assertThat(idObj).isNotEqualTo(null);
+ assertThat(idObj.getStringId().length()).isGreaterThan(0);
+ }
+
+ @Test
+ public void testRecordingSession() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ MediaMetricsManager manager = context.getSystemService(MediaMetricsManager.class);
+ RecordingSession s = manager.createRecordingSession();
+
+ assertThat(s).isNotEqualTo(null);
+ LogSessionId idObj = s.getSessionId();
+ assertThat(idObj).isNotEqualTo(null);
+ assertThat(idObj.getStringId().length()).isGreaterThan(0);
+ }
}
diff --git a/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
index 43548043..313bc16 100644
--- a/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
+++ b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
@@ -30,6 +30,8 @@
import com.android.tradefed.testtype.IBuildReceiver;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
import java.util.List;
public class MediaMetricsAtomTests extends DeviceTestCase implements IBuildReceiver {
@@ -188,5 +190,35 @@
assertThat(result.getNetworkBytesRead()).isEqualTo(102400);
assertThat(result.getLocalBytesRead()).isEqualTo(2000);
assertThat(result.getNetworkTransferDurationMillis()).isEqualTo(6000);
+ // TODO: needs Base64 decoders to verify the data
+ assertThat(result.getDrmSessionId()).isNotEqualTo(null);
}
+
+ public void testSessionId() throws Exception {
+ ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+ AtomsProto.Atom.MEDIAMETRICS_PLAYBACK_REPORTED_FIELD_NUMBER);
+ DeviceUtils.runDeviceTests(
+ getDevice(),
+ TEST_PKG,
+ "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+ "testSessionId");
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ assertThat(data.size()).isEqualTo(0);
+ }
+
+ public void testRecordingSession() throws Exception {
+ ConfigUtils.uploadConfigForPushedAtom(getDevice(), TEST_PKG,
+ AtomsProto.Atom.MEDIAMETRICS_PLAYBACK_REPORTED_FIELD_NUMBER);
+ DeviceUtils.runDeviceTests(
+ getDevice(),
+ TEST_PKG,
+ "android.media.metrics.cts.MediaMetricsAtomHostSideTests",
+ "testRecordingSession");
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ assertThat(data.size()).isEqualTo(0);
+ }
}
diff --git a/hostsidetests/os/src/android/os/cts/OsHostTests.java b/hostsidetests/os/src/android/os/cts/OsHostTests.java
index 5aea564..37f3e78 100644
--- a/hostsidetests/os/src/android/os/cts/OsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/OsHostTests.java
@@ -16,8 +16,6 @@
package android.os.cts;
-import static com.google.common.truth.Truth.assertThat;
-
import android.platform.test.annotations.AppModeFull;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -29,15 +27,11 @@
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;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -56,15 +50,6 @@
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";
- private static final String FILTER_VERIFIER_REGEXP =
- "Verifying IntentFilter\\..* package:\"" + HOST_VERIFICATION_PKG + "\"";
- private static final Pattern HOST_PATTERN = Pattern.compile(".*hosts:\"(.*?)\"");
- // domains that should be validated against given our test apk
- private static final String HOST_WILDCARD = "wildcard.tld";
-
/**
* A reference to the device under test.
*/
@@ -148,59 +133,4 @@
assertTrue("Looking for nonexistence of service process " + pid,
lsOut.contains("No such file"));
}
-
- public void testIntentFilterHostValidation() throws Exception {
- String line = null;
- try {
- // Clean slate in case of earlier aborted run
- mDevice.uninstallPackage(HOST_VERIFICATION_PKG);
-
- final String[] options = { AbiUtils.createAbiFlag(mAbi.getName()) };
- final int currentUser = mDevice.getCurrentUser();
-
- mDevice.clearLogcat();
-
- final String errorString;
- errorString = mDevice.installPackageForUser(getTestAppFile(HOST_VERIFICATION_APK),
- false /* = reinstall? */, currentUser, options);
-
- assertNull("Couldn't install web intent filter sample apk in user " +
- currentUser + " : " + errorString, errorString);
-
- String logs = mDevice.executeAdbCommand("logcat", "-v", "brief", "-d");
- boolean foundVerifierOutput = false;
- Pattern verifierPattern = Pattern.compile(FILTER_VERIFIER_REGEXP);
- Scanner scanner = new Scanner(logs);
- while (scanner.hasNextLine()) {
- line = scanner.nextLine();
- Matcher verifierMatcher = verifierPattern.matcher(line);
- if (verifierMatcher.find()) {
- Matcher m = HOST_PATTERN.matcher(line);
- assertTrue(m.find());
- final String hostgroup = m.group(1);
- HashSet<String> allHosts = new HashSet<>(
- Arrays.asList(hostgroup.split(" ")));
- assertThat(allHosts).containsExactly(HOST_WILDCARD);
- foundVerifierOutput = true;
- break;
- }
- }
-
- assertTrue(foundVerifierOutput);
- } catch (Exception e) {
- fail("Unable to parse verification results: " + e.getMessage()
- + " line=" + line);
- } finally {
- // Finally, uninstall the app
- mDevice.uninstallPackage(HOST_VERIFICATION_PKG);
- }
- }
-
- /*
- * Helper: find a test apk
- */
- private File getTestAppFile(String fileName) throws FileNotFoundException {
- CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
- return buildHelper.getTestFile(fileName);
- }
}
diff --git a/hostsidetests/os/test-apps/HostLinkVerificationApp/Android.bp b/hostsidetests/os/test-apps/HostLinkVerificationApp/Android.bp
deleted file mode 100644
index 3fb73fe..0000000
--- a/hostsidetests/os/test-apps/HostLinkVerificationApp/Android.bp
+++ /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 {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
- name: "CtsHostLinkVerificationApp",
- defaults: ["cts_support_defaults"],
- sdk_version: "current",
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- ],
-}
diff --git a/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml b/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml
deleted file mode 100644
index ace8c82..0000000
--- a/hostsidetests/os/test-apps/HostLinkVerificationApp/AndroidManifest.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<!-- Declare the contents of this Android application. The namespace
- attribute brings in the Android platform namespace, and the package
- supplies a unique name for the application. When writing your
- own application, the package name must be changed from "com.example.*"
- to come from a domain that you own or have control over. -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.openlinksskeleton"
- android:versionCode="1"
- android:versionName="1.0">
-
- <application android:label="Open Links Skeleton"
- android:hasCode="false">
-
- <activity android:name="DummyWebLinkActivity"
- android:exported="true">
- <intent-filter android:autoVerify="true">
- <action android:name="android.intent.action.VIEW"/>
- <category android:name="android.intent.category.DEFAULT"/>
- <category android:name="android.intent.category.BROWSABLE"/>
- <data android:scheme="http"/>
- <data android:scheme="https"/>
- <data android:host="*.wildcard.tld"/>
- </intent-filter>
-
- <!-- Also make sure that verification picks up web navigation
- handling even when the filter matches non-web schemes -->
- <intent-filter>
- <action android:name="android.intent.action.VIEW"/>
- <category android:name="android.intent.category.DEFAULT"/>
- <category android:name="android.intent.category.BROWSABLE"/>
- <data android:scheme="http"/>
- <data android:scheme="https"/>
- <data android:scheme="nonweb"/>
- <data android:host="explicit.example.com"/>
- </intent-filter>
- </activity>
-
- </application>
-</manifest>
diff --git a/hostsidetests/packagemanager/domainverification/OWNERS b/hostsidetests/packagemanager/domainverification/OWNERS
new file mode 100644
index 0000000..dd85fa9
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 36137
+chiuwinson@google.com
+patb@google.com
+toddke@google.com
diff --git a/hostsidetests/packagemanager/domainverification/apps/calling/Android.bp b/hostsidetests/packagemanager/domainverification/apps/calling/Android.bp
new file mode 100644
index 0000000..aa8008a
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/calling/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsDomainVerificationTestCallingApp",
+ srcs: [ "src/**/*.kt" ],
+ defaults: ["cts_defaults"],
+ sdk_version: "test_current",
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "CtsDomainVerificationAndroidConstantsLibrary",
+ "CtsDomainVerificationJavaConstantsLibrary",
+ "CtsDomainVerificationVerificationsLibrary",
+ "junit",
+ "kotlin-reflect",
+ "truth-prebuilt",
+ ]
+}
diff --git a/hostsidetests/packagemanager/domainverification/apps/calling/AndroidManifest.xml b/hostsidetests/packagemanager/domainverification/apps/calling/AndroidManifest.xml
new file mode 100644
index 0000000..ee120fd
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/calling/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.packagemanager.verify.domain.callingapp">
+
+ <application android:label="Calling Test App">
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".CallingActivity" android:exported="true"/>
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.packagemanager.verify.domain.callingapp" />
+
+ <queries>
+ <package android:name="com.android.cts.packagemanager.verify.domain.declaringapp1"/>
+ <package android:name="com.android.cts.packagemanager.verify.domain.declaringapp2"/>
+ <intent>
+ <action android:name="android.intent.action.VIEW" />
+ <data android:scheme="https" />
+ </intent>
+ <intent>
+ <action android:name="android.intent.action.VIEW" />
+ <data android:scheme="http" />
+ </intent>
+ </queries>
+
+</manifest>
+
diff --git a/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/CallingActivity.kt b/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/CallingActivity.kt
new file mode 100644
index 0000000..1b1af08
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/CallingActivity.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain.callingapp
+
+import android.app.Activity
+
+class CallingActivity : Activity()
diff --git a/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/DomainVerificationCallingAppTests.kt b/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/DomainVerificationCallingAppTests.kt
new file mode 100644
index 0000000..24653b3
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/DomainVerificationCallingAppTests.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain.callingapp
+
+import android.app.Instrumentation
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.cts.packagemanager.verify.domain.SharedVerifications
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class DomainVerificationCallingAppTests {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context = instrumentation.targetContext
+
+ @Before
+ @After
+ fun reset() {
+ SharedVerifications.reset(context)
+ }
+
+ @Test
+ fun verifyUnownedDomains() {
+ SharedVerifications.verifyDomains(context)
+ }
+}
diff --git a/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/DomainVerificationIntentHostTimedTests.kt b/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/DomainVerificationIntentHostTimedTests.kt
new file mode 100644
index 0000000..0d2a790
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/calling/src/com/android/cts/packagemanager/verify/domain/callingapp/DomainVerificationIntentHostTimedTests.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain.callingapp
+
+import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_1_COMPONENT
+import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_2_COMPONENT
+import com.android.cts.packagemanager.verify.domain.android.DomainVerificationIntentTestBase
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_2
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class DomainVerificationIntentHostTimedTests : DomainVerificationIntentTestBase() {
+
+ @Test
+ fun multipleVerifiedTakeLastFirstInstall() {
+ setAppLinks(DECLARING_PKG_NAME_2, true, DOMAIN_1, DOMAIN_2)
+
+ assertResolvesTo(DECLARING_PKG_2_COMPONENT)
+
+ setAppLinks(DomainUtils.DECLARING_PKG_NAME_1, true, DOMAIN_1, DOMAIN_2)
+
+ assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+ // Re-approve 2 and ensure this doesn't affect anything
+ setAppLinks(DECLARING_PKG_NAME_2, true, DOMAIN_1, DOMAIN_2)
+
+ assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+ }
+}
diff --git a/hostsidetests/packagemanager/domainverification/apps/declaring/Android.bp b/hostsidetests/packagemanager/domainverification/apps/declaring/Android.bp
new file mode 100644
index 0000000..b8e7319
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/declaring/Android.bp
@@ -0,0 +1,53 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+ name: "CtsDomainVerificationTestDeclaringAppDefaults",
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "CtsDomainVerificationAndroidConstantsLibrary",
+ "CtsDomainVerificationJavaConstantsLibrary",
+ "CtsDomainVerificationVerificationsLibrary",
+ "junit",
+ "truth-prebuilt",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsDomainVerificationTestDeclaringApp1",
+ srcs: ["src/**/*.kt"],
+ defaults: [
+ "cts_defaults",
+ "CtsDomainVerificationTestDeclaringAppDefaults",
+ ],
+ sdk_version: "test_current",
+ aaptflags: ["--rename-manifest-package com.android.cts.packagemanager.verify.domain.declaringapp1"],
+}
+
+android_test_helper_app {
+ name: "CtsDomainVerificationTestDeclaringApp2",
+ srcs: ["src/**/*.kt"],
+ defaults: [
+ "cts_defaults",
+ "CtsDomainVerificationTestDeclaringAppDefaults",
+ ],
+ sdk_version: "test_current",
+ aaptflags: ["--rename-manifest-package com.android.cts.packagemanager.verify.domain.declaringapp2"],
+}
diff --git a/hostsidetests/packagemanager/domainverification/apps/declaring/AndroidManifest.xml b/hostsidetests/packagemanager/domainverification/apps/declaring/AndroidManifest.xml
new file mode 100644
index 0000000..fcdf59c
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/declaring/AndroidManifest.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.packagemanager.verify.domain.declaringapp">
+
+ <application android:label="Declaring Test App">
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".DeclaringActivity" android:exported="true">
+
+ <!-- Normal success case, declaring valid domain with autoVerify -->
+ <intent-filter android:autoVerify="true">
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.BROWSABLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:scheme="http" />
+ <data android:scheme="https" />
+ <data android:host="com.android.cts.packagemanager.verify.domain.1.pmctstesting" />
+ <data android:host="invalid1" />
+ </intent-filter>
+
+ <!-- Valid intent-filter, but missing autoVerify -->
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.BROWSABLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:scheme="http" />
+ <data android:host="com.android.cts.packagemanager.verify.domain.2.pmctstesting" />
+ <data android:host="invalid2." />
+ </intent-filter>
+
+ <!-- Missing http, still accepted -->
+ <intent-filter android:autoVerify="true">
+ <action android:name="android.intent.action.VIEW" />
+
+ <category android:name="android.intent.category.BROWSABLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:scheme="https" />
+ <data android:host="com.android.cts.packagemanager.verify.domain.3.pmctstesting" />
+ <data android:host=".invalid3" />
+ </intent-filter>
+
+ <!-- Missing DEFAULT, rejected -->
+ <intent-filter android:autoVerify="true">
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.BROWSABLE" />
+
+ <data android:scheme="https" />
+ <data android:host="com.android.cts.packagemanager.verify.domain.4.pmctstesting" />
+ <data android:host="invalid4" />
+ </intent-filter>
+
+ <!-- Missing BROWSABLE, rejected -->
+ <intent-filter android:autoVerify="true">
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:scheme="https" />
+ <data android:host="com.android.cts.packagemanager.verify.domain.5.pmctstesting" />
+ <data android:host="invalid5" />
+ </intent-filter>
+
+ <!-- Missing VIEW, rejected -->
+ <intent-filter android:autoVerify="true">
+ <category android:name="android.intent.category.BROWSABLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+
+ <data android:scheme="https" />
+ <data android:host="com.android.cts.packagemanager.verify.domain.6.pmctstesting" />
+ <data android:host="invalid6" />
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.packagemanager.verify.domain.declaringapp1" />
+
+ <queries>
+ <intent>
+ <action android:name="android.intent.action.VIEW" />
+ <data android:scheme="https" />
+ </intent>
+ <intent>
+ <action android:name="android.intent.action.VIEW" />
+ <data android:scheme="http" />
+ </intent>
+ </queries>
+
+</manifest>
+
diff --git a/hostsidetests/packagemanager/domainverification/apps/declaring/src/com/android/cts/packagemanager/verify/domain/declaringapp/DeclaringActivity.kt b/hostsidetests/packagemanager/domainverification/apps/declaring/src/com/android/cts/packagemanager/verify/domain/declaringapp/DeclaringActivity.kt
new file mode 100644
index 0000000..8d440ec
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/declaring/src/com/android/cts/packagemanager/verify/domain/declaringapp/DeclaringActivity.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain.declaringapp
+
+import android.app.Activity
+
+class DeclaringActivity : Activity()
diff --git a/hostsidetests/packagemanager/domainverification/apps/declaring/src/com/android/cts/packagemanager/verify/domain/declaringapp/DomainVerificationDeclaringAppTests.kt b/hostsidetests/packagemanager/domainverification/apps/declaring/src/com/android/cts/packagemanager/verify/domain/declaringapp/DomainVerificationDeclaringAppTests.kt
new file mode 100644
index 0000000..a01ad93
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/apps/declaring/src/com/android/cts/packagemanager/verify/domain/declaringapp/DomainVerificationDeclaringAppTests.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain.declaringapp
+
+import android.app.Instrumentation
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.cts.packagemanager.verify.domain.SharedVerifications
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class DomainVerificationDeclaringAppTests {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context = instrumentation.targetContext
+
+ @Before
+ @After
+ fun reset() {
+ SharedVerifications.reset(context)
+ }
+
+ @Test
+ fun verifyOwnDomains() {
+ SharedVerifications.verifyDomains(context)
+ }
+}
diff --git a/hostsidetests/packagemanager/domainverification/device/Android.bp b/hostsidetests/packagemanager/domainverification/device/Android.bp
new file mode 100644
index 0000000..476264fc
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/device/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsDomainVerificationDeviceTestCases",
+ srcs: [ "src/**/*.kt" ],
+ defaults: ["cts_defaults"],
+ sdk_version: "test_current",
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "CtsDomainVerificationAndroidConstantsLibrary",
+ "CtsDomainVerificationJavaConstantsLibrary",
+ "junit",
+ "truth-prebuilt",
+ ],
+ data: [
+ ":CtsDomainVerificationTestDeclaringApp1",
+ ":CtsDomainVerificationTestDeclaringApp2",
+ ],
+}
diff --git a/hostsidetests/packagemanager/domainverification/device/AndroidManifest.xml b/hostsidetests/packagemanager/domainverification/device/AndroidManifest.xml
new file mode 100644
index 0000000..9afe67d
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/device/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.packagemanager.verify.domain.device">
+
+ <application android:label="Device Test App">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.packagemanager.verify.domain.device" />
+
+ <queries>
+ <package android:name="com.android.cts.packagemanager.verify.domain.declaringapp1"/>
+ <package android:name="com.android.cts.packagemanager.verify.domain.declaringapp2"/>
+ <intent>
+ <action android:name="android.intent.action.VIEW" />
+ <data android:scheme="https" />
+ </intent>
+ <intent>
+ <action android:name="android.intent.action.VIEW" />
+ <data android:scheme="http" />
+ </intent>
+ </queries>
+
+</manifest>
+
diff --git a/hostsidetests/packagemanager/domainverification/device/AndroidTest.xml b/hostsidetests/packagemanager/domainverification/device/AndroidTest.xml
new file mode 100644
index 0000000..4cdf6b7
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/device/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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 package manager metrics device 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="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_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" />
+ <option name="test-file-name" value="CtsDomainVerificationDeviceTestCases.apk" />
+ <option name="test-file-name" value="CtsDomainVerificationTestDeclaringApp1.apk" />
+ <option name="test-file-name" value="CtsDomainVerificationTestDeclaringApp2.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.cts.packagemanager.verify.domain.device" />
+ </test>
+</configuration>
+
diff --git a/hostsidetests/packagemanager/domainverification/device/src/com/android/cts/packagemanager/verify/domain/device/CallingActivity.kt b/hostsidetests/packagemanager/domainverification/device/src/com/android/cts/packagemanager/verify/domain/device/CallingActivity.kt
new file mode 100644
index 0000000..1b1af08
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/device/src/com/android/cts/packagemanager/verify/domain/device/CallingActivity.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain.callingapp
+
+import android.app.Activity
+
+class CallingActivity : Activity()
diff --git a/hostsidetests/packagemanager/domainverification/device/src/com/android/cts/packagemanager/verify/domain/device/DomainVerificationIntentStandaloneTests.kt b/hostsidetests/packagemanager/domainverification/device/src/com/android/cts/packagemanager/verify/domain/device/DomainVerificationIntentStandaloneTests.kt
new file mode 100644
index 0000000..8923993
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/device/src/com/android/cts/packagemanager/verify/domain/device/DomainVerificationIntentStandaloneTests.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain.device
+
+import android.content.pm.verify.domain.DomainVerificationUserState
+import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_1_COMPONENT
+import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_2_COMPONENT
+import com.android.cts.packagemanager.verify.domain.android.DomainVerificationIntentTestBase
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_2
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class DomainVerificationIntentStandaloneTests : DomainVerificationIntentTestBase() {
+
+ @Test
+ fun launchVerified() {
+ setAppLinks(DECLARING_PKG_NAME_1, true, DOMAIN_1, DOMAIN_2)
+
+ val hostToStateMap = manager.getDomainVerificationUserState(DECLARING_PKG_NAME_1)
+ ?.hostToStateMap
+
+ assertThat(hostToStateMap?.get(DOMAIN_1))
+ .isEqualTo(DomainVerificationUserState.DOMAIN_STATE_VERIFIED)
+
+ // The 2nd domain isn't marked as auto verify
+ assertThat(hostToStateMap?.get(DOMAIN_2))
+ .isEqualTo(DomainVerificationUserState.DOMAIN_STATE_NONE)
+
+ assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+ setAppLinks(DECLARING_PKG_NAME_1, false, DOMAIN_1, DOMAIN_2)
+
+ assertResolvesTo(browsers)
+ }
+
+ @Test
+ fun launchSelected() {
+ setAppLinks(DECLARING_PKG_NAME_1, false, DOMAIN_1, DOMAIN_2)
+ setAppLinksUserSelection(DECLARING_PKG_NAME_1, userId, true, DOMAIN_1, DOMAIN_2)
+
+ val hostToStateMap = manager.getDomainVerificationUserState(DECLARING_PKG_NAME_1)
+ ?.hostToStateMap
+
+ assertThat(hostToStateMap?.get(DOMAIN_1))
+ .isEqualTo(DomainVerificationUserState.DOMAIN_STATE_SELECTED)
+ assertThat(hostToStateMap?.get(DOMAIN_2))
+ .isEqualTo(DomainVerificationUserState.DOMAIN_STATE_SELECTED)
+
+ assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+ setAppLinksUserSelection(DECLARING_PKG_NAME_1, userId, false, DOMAIN_1, DOMAIN_2)
+
+ assertResolvesTo(browsers)
+ }
+
+ @Test
+ fun verifiedOverSelected() {
+ setAppLinksUserSelection(DECLARING_PKG_NAME_1, userId, true, DOMAIN_1, DOMAIN_2)
+
+ assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+ setAppLinks(DECLARING_PKG_NAME_2, true, DOMAIN_1, DOMAIN_2)
+
+ assertResolvesTo(DECLARING_PKG_2_COMPONENT)
+
+ setAppLinks(DECLARING_PKG_NAME_2, false, DOMAIN_1, DOMAIN_2)
+
+ // Assert that if 2 is approved and denied,
+ // 1 will lose approval and must be re-enabled manually
+ assertResolvesTo(browsers)
+ }
+
+ @Test
+ fun selectedOverSelected() {
+ setAppLinksUserSelection(DECLARING_PKG_NAME_1, userId, true, DOMAIN_1, DOMAIN_2)
+
+ assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+ setAppLinksUserSelection(DECLARING_PKG_NAME_2, userId, true, DOMAIN_1, DOMAIN_2)
+
+ assertResolvesTo(DECLARING_PKG_2_COMPONENT)
+
+ setAppLinksUserSelection(DECLARING_PKG_NAME_2, userId, false, DOMAIN_1, DOMAIN_2)
+
+ // Assert that if 2 is enabled and disabled,
+ // 1 will lose approval and must be re-enabled manually
+ assertResolvesTo(browsers)
+ }
+
+ @Test
+ fun selectedOverVerifiedFails() {
+ setAppLinks(DECLARING_PKG_NAME_1, true, DOMAIN_1, DOMAIN_2)
+
+ assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+ setAppLinksUserSelection(DECLARING_PKG_NAME_2, userId, true, DOMAIN_1, DOMAIN_2)
+
+ assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+ }
+
+ @Test
+ fun disableHandlingWhenVerified() {
+ setAppLinks(DECLARING_PKG_NAME_1, true, DOMAIN_1, DOMAIN_2)
+
+ assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+ setAppLinksAllowed(DECLARING_PKG_NAME_1, userId, false)
+
+ assertResolvesTo(browsers)
+ }
+
+ @Test
+ fun disableHandlingWhenSelected() {
+ setAppLinksUserSelection(DECLARING_PKG_NAME_1, userId, true, DOMAIN_1, DOMAIN_2)
+
+ assertResolvesTo(DECLARING_PKG_1_COMPONENT)
+
+ setAppLinksAllowed(DECLARING_PKG_NAME_1, userId, false)
+
+ assertResolvesTo(browsers)
+ }
+}
diff --git a/hostsidetests/packagemanager/domainverification/host/Android.bp b/hostsidetests/packagemanager/domainverification/host/Android.bp
new file mode 100644
index 0000000..dfa1e53
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/host/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+ name: "CtsDomainVerificationHostTestCases",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.kt"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+ libs: [
+ "cts-tradefed",
+ "tradefed",
+ "compatibility-host-util",
+ ],
+ static_libs: [
+ "cts-host-utils",
+ "CtsDomainVerificationJavaConstantsLibraryHost",
+ ],
+ data: [
+ ":CtsDomainVerificationTestCallingApp",
+ ":CtsDomainVerificationTestDeclaringApp1",
+ ":CtsDomainVerificationTestDeclaringApp2",
+ ],
+}
diff --git a/hostsidetests/packagemanager/domainverification/host/AndroidTest.xml b/hostsidetests/packagemanager/domainverification/host/AndroidTest.xml
new file mode 100644
index 0000000..6090314
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/host/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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 package manager metrics host 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="multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_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" />
+ <option name="test-file-name" value="CtsDomainVerificationTestCallingApp.apk" />
+ </target_preparer>
+
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsDomainVerificationHostTestCases.jar" />
+ </test>
+</configuration>
+
diff --git a/hostsidetests/packagemanager/domainverification/host/src/com/android/cts/packagemanager/verify/domain/host/DomainVerificationIntentHostTimedTests.kt b/hostsidetests/packagemanager/domainverification/host/src/com/android/cts/packagemanager/verify/domain/host/DomainVerificationIntentHostTimedTests.kt
new file mode 100644
index 0000000..d0c95ad
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/host/src/com/android/cts/packagemanager/verify/domain/host/DomainVerificationIntentHostTimedTests.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain.host
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.CALLING_PKG_NAME
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_APK_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_APK_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_2
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class DomainVerificationIntentHostTimedTests : BaseHostJUnit4Test() {
+
+ private val buildHelper by lazy { CompatibilityBuildHelper(build) }
+
+ @Before
+ @After
+ fun uninstall() {
+ device.uninstallPackage(DECLARING_PKG_NAME_1)
+ device.uninstallPackage(DECLARING_PKG_NAME_2)
+ }
+
+ @Test
+ fun multipleVerifiedTakeLastFirstInstall() {
+ installPackage(DECLARING_PKG_APK_2)
+
+ // Ensure a later install time
+ Thread.sleep(500)
+
+ installPackage(DECLARING_PKG_APK_1)
+
+ // Install an update, which should not take precedence
+ installPackage(DECLARING_PKG_APK_2, reinstall = true)
+
+ runDeviceTests(DeviceTestRunOptions(CALLING_PKG_NAME).apply {
+ testClassName = "$CALLING_PKG_NAME.DomainVerificationIntentHostTimedTests"
+ })
+ }
+
+ private fun installPackage(apkName: DomainUtils.ApkName, reinstall: Boolean = false) =
+ device.installPackage(buildHelper.getTestFile(apkName.value), reinstall)
+}
diff --git a/hostsidetests/packagemanager/domainverification/host/src/com/android/cts/packagemanager/verify/domain/host/DomainVerificationTests.kt b/hostsidetests/packagemanager/domainverification/host/src/com/android/cts/packagemanager/verify/domain/host/DomainVerificationTests.kt
new file mode 100644
index 0000000..0bf2145
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/host/src/com/android/cts/packagemanager/verify/domain/host/DomainVerificationTests.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain.host
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.CALLING_PKG_NAME
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_APK_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_APK_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_BASE
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class DomainVerificationTests : BaseHostJUnit4Test() {
+
+ private val buildHelper by lazy { CompatibilityBuildHelper(build) }
+
+ @Before
+ @After
+ fun uninstall() {
+ device.uninstallPackage(DECLARING_PKG_NAME_1)
+ device.uninstallPackage(DECLARING_PKG_NAME_2)
+ }
+
+ @Test
+ fun declaredDomainSet() {
+ installPackage(DECLARING_PKG_APK_1)
+ runDeviceTests(DeviceTestRunOptions(DECLARING_PKG_NAME_1).apply {
+ // The base name is used as the code package does not change with
+ // the manifest rename that splits the packages into 1 and 2 variants.
+ testClassName = "$DECLARING_PKG_NAME_BASE.DomainVerificationDeclaringAppTests"
+ testMethodName = "verifyOwnDomains"
+ })
+ }
+
+ @Test
+ fun verifyDomains() {
+ installPackage(DECLARING_PKG_APK_1)
+ installPackage(DECLARING_PKG_APK_2)
+ runDeviceTests(DeviceTestRunOptions(CALLING_PKG_NAME).apply {
+ testClassName = "$CALLING_PKG_NAME.DomainVerificationCallingAppTests"
+ })
+ }
+
+ private fun installPackage(apkName: DomainUtils.ApkName, reinstall: Boolean = false) =
+ device.installPackage(buildHelper.getTestFile(apkName.value), reinstall)
+}
diff --git a/tests/contentcaptureservice/aidl/Android.bp b/hostsidetests/packagemanager/domainverification/lib/constants/android/Android.bp
similarity index 61%
copy from tests/contentcaptureservice/aidl/Android.bp
copy to hostsidetests/packagemanager/domainverification/lib/constants/android/Android.bp
index 7e91137..e956874 100644
--- a/tests/contentcaptureservice/aidl/Android.bp
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/android/Android.bp
@@ -1,4 +1,3 @@
-//
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,15 +11,23 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-//
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
-java_library {
- name: "OutOfPackageDataSharingServiceAidl",
- srcs: [
- "src/**/*.aidl",
+android_library {
+ name: "CtsDomainVerificationAndroidConstantsLibrary",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "CtsDomainVerificationJavaConstantsLibrary",
+ "CtsDomainVerificationVerificationsLibrary",
+ "junit",
+ "kotlin-reflect",
+ "truth-prebuilt",
],
}
diff --git a/hostsidetests/packagemanager/domainverification/lib/constants/android/AndroidManifest.xml b/hostsidetests/packagemanager/domainverification/lib/constants/android/AndroidManifest.xml
new file mode 100644
index 0000000..f332a9e
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/android/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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 package="com.android.cts.packagemanager.verify.domain.constants.android">
+ <application/>
+</manifest>
+
diff --git a/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/DomainUtils.kt b/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/DomainUtils.kt
new file mode 100644
index 0000000..156bf77
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/DomainUtils.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain.android
+
+import android.content.ComponentName
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_BASE
+
+object DomainUtils {
+ val DECLARING_PKG_1_COMPONENT =
+ ComponentName(DECLARING_PKG_NAME_1, "$DECLARING_PKG_NAME_BASE.DeclaringActivity")
+ val DECLARING_PKG_2_COMPONENT =
+ ComponentName(DECLARING_PKG_NAME_2, "$DECLARING_PKG_NAME_BASE.DeclaringActivity")
+}
diff --git a/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/DomainVerificationIntentTestBase.kt b/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/DomainVerificationIntentTestBase.kt
new file mode 100644
index 0000000..7c02587
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/DomainVerificationIntentTestBase.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain.android
+
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.net.Uri
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.ShellUtils
+import com.android.cts.packagemanager.verify.domain.SharedVerifications
+import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_1_COMPONENT
+import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_2_COMPONENT
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_UNHANDLED
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+abstract class DomainVerificationIntentTestBase {
+
+ companion object {
+
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun parameters() = IntentVariant.values()
+ }
+
+ @Parameterized.Parameter(0)
+ lateinit var intentVariant: IntentVariant
+
+ protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ protected val context = instrumentation.targetContext
+ protected val packageManager = context.packageManager
+ protected val userId = context.userId
+ protected val manager = context.getSystemService(DomainVerificationManager::class.java)!!
+
+ protected lateinit var intent: Intent
+
+ protected lateinit var browsers: List<ComponentName>
+ protected lateinit var allResults: List<ComponentName>
+
+ @Before
+ fun findBrowsers() {
+ intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://$DOMAIN_1"))
+ .applyIntentVariant(intentVariant)
+
+ browsers = Intent(Intent.ACTION_VIEW, Uri.parse("https://$DOMAIN_UNHANDLED"))
+ .applyIntentVariant(intentVariant)
+ .let { context.packageManager.queryIntentActivities(it, 0) }
+ .map { it.activityInfo }
+ .map { ComponentName(it.packageName, it.name) }
+ .also { assumeTrue(it.isNotEmpty()) }
+
+ val allResults = browsers.toMutableList()
+ try {
+ packageManager.getPackageInfo(DECLARING_PKG_NAME_1, 0)
+ allResults += DECLARING_PKG_1_COMPONENT
+ } catch (ignored: PackageManager.NameNotFoundException) {
+ }
+ try {
+ packageManager.getPackageInfo(DECLARING_PKG_NAME_2, 0)
+ allResults += DECLARING_PKG_2_COMPONENT
+ } catch (ignored: PackageManager.NameNotFoundException) {
+ }
+
+ this.allResults = allResults
+ assertResolvesTo(browsers)
+ }
+
+ @Before
+ @After
+ fun reset() {
+ SharedVerifications.reset(context)
+ }
+
+ protected fun runShellCommand(vararg commands: String) = commands.forEach {
+ assertThat(ShellUtils.runShellCommand(it)).isEmpty()
+ }
+
+ protected fun assertResolvesTo(result: ComponentName) = assertResolvesTo(listOf(result))
+
+ protected fun assertResolvesTo(packageNames: Collection<ComponentName>) {
+ // Pass MATCH_DEFAULT_ONLY to mirror startActivity resolution
+ assertThat(packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
+ .map { it.activityInfo }
+ .map { ComponentName(it.packageName, it.name) })
+ .containsExactlyElementsIn(packageNames)
+
+ if (intent.hasCategory(Intent.CATEGORY_DEFAULT)) {
+ // Verify explicit DEFAULT mirrors MATCH_DEFAULT_ONLY
+ assertThat(packageManager.queryIntentActivities(intent, 0)
+ .map { it.activityInfo }
+ .map { ComponentName(it.packageName, it.name) })
+ .containsExactlyElementsIn(packageNames)
+ } else {
+ // Verify that non-DEFAULT match returns all results
+ assertThat(packageManager.queryIntentActivities(intent, 0)
+ .map { it.activityInfo }
+ .map { ComponentName(it.packageName, it.name) })
+ .containsExactlyElementsIn(allResults)
+ }
+ }
+
+ fun resetAppLinks(packageName: String) {
+ runShellCommand(DomainUtils.resetAppLinks(packageName))
+ }
+
+ fun setAppLinks(packageName: String, enabled: Boolean, vararg domains: String) {
+ val state = "STATE_APPROVED".takeIf { enabled } ?: "STATE_DENIED"
+ runShellCommand(DomainUtils.setAppLinks(packageName, state, *domains))
+ }
+
+ fun setAppLinksAllowed(packageName: String, userId: Int, enabled: Boolean) {
+ runShellCommand(DomainUtils.setAppLinksAllowed(packageName, userId, enabled))
+ }
+
+ fun setAppLinksUserSelection(
+ packageName: String,
+ userId: Int,
+ enabled: Boolean,
+ vararg domains: String
+ ) {
+ runShellCommand(
+ DomainUtils.setAppLinksUserSelection(packageName, userId, enabled, *domains)
+ )
+ }
+}
diff --git a/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/IntentVariant.kt b/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/IntentVariant.kt
new file mode 100644
index 0000000..91e0d9f
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/android/src/com/android/cts/packagemanager/verify/domain/android/IntentVariant.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain.android
+
+import android.content.Intent
+
+enum class IntentVariant {
+ BASE,
+ BROWSABLE,
+ DEFAULT,
+ BROWSABLE_DEFAULT
+}
+
+internal fun Intent.applyIntentVariant(intentVariant: IntentVariant) = apply {
+ when (intentVariant) {
+ IntentVariant.BASE -> {
+ }
+ IntentVariant.BROWSABLE -> addCategory(Intent.CATEGORY_BROWSABLE)
+ IntentVariant.DEFAULT -> addCategory(Intent.CATEGORY_DEFAULT)
+ IntentVariant.BROWSABLE_DEFAULT -> {
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ }
+ }
+}
diff --git a/tests/contentcaptureservice/aidl/Android.bp b/hostsidetests/packagemanager/domainverification/lib/constants/java/Android.bp
similarity index 73%
copy from tests/contentcaptureservice/aidl/Android.bp
copy to hostsidetests/packagemanager/domainverification/lib/constants/java/Android.bp
index 7e91137..f67ff2a 100644
--- a/tests/contentcaptureservice/aidl/Android.bp
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/java/Android.bp
@@ -1,4 +1,3 @@
-//
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,15 +11,19 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-//
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
java_library {
- name: "OutOfPackageDataSharingServiceAidl",
- srcs: [
- "src/**/*.aidl",
- ],
+ name: "CtsDomainVerificationJavaConstantsLibrary",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.kt"],
+}
+
+java_library_host {
+ name: "CtsDomainVerificationJavaConstantsLibraryHost",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.kt"],
}
diff --git a/hostsidetests/packagemanager/domainverification/lib/constants/java/src/com/android/cts/packagemanager/verify/domain/java/DomainUtils.kt b/hostsidetests/packagemanager/domainverification/lib/constants/java/src/com/android/cts/packagemanager/verify/domain/java/DomainUtils.kt
new file mode 100644
index 0000000..141ba59
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/java/src/com/android/cts/packagemanager/verify/domain/java/DomainUtils.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain.java
+
+object DomainUtils {
+ private const val BASE_PACKAGE = "com.android.cts.packagemanager.verify.domain"
+
+ const val DOMAIN_TLD = "pmctstesting"
+ const val DOMAIN_1 = "$BASE_PACKAGE.1.$DOMAIN_TLD"
+ const val DOMAIN_2 = "$BASE_PACKAGE.2.$DOMAIN_TLD"
+ const val DOMAIN_3 = "$BASE_PACKAGE.3.$DOMAIN_TLD"
+ const val DOMAIN_UNHANDLED = "$BASE_PACKAGE.unhandled.$DOMAIN_TLD"
+
+ const val CALLING_PKG_NAME = "$BASE_PACKAGE.callingapp"
+
+ const val DECLARING_PKG_NAME_BASE = "$BASE_PACKAGE.declaringapp"
+ private const val DECLARING_PKG_APK_BASE = "CtsDomainVerificationTestDeclaringApp"
+
+ const val DECLARING_PKG_NAME_1 = "${DECLARING_PKG_NAME_BASE}1"
+ val DECLARING_PKG_APK_1 = ApkName("${DECLARING_PKG_APK_BASE}1.apk")
+
+ const val DECLARING_PKG_NAME_2 = "${DECLARING_PKG_NAME_BASE}2"
+ val DECLARING_PKG_APK_2 = ApkName("${DECLARING_PKG_APK_BASE}2.apk")
+
+ inline class ApkName(val value: String)
+
+ val ALL_PACKAGES = listOf(CALLING_PKG_NAME, DECLARING_PKG_NAME_1, DECLARING_PKG_NAME_2)
+
+ fun resetAppLinks(packageName: String) = "pm reset-app-links $packageName"
+
+ fun setAppLinks(packageName: String, state: String, vararg domains: String) =
+ "pm set-app-links --package $packageName $state " +
+ domains.joinToString(separator = " ")
+
+ fun setAppLinksAllowed(packageName: String, userId: Int, enabled: Boolean) =
+ "pm set-app-links-allowed --package $packageName --user $userId $enabled"
+
+ fun setAppLinksUserSelection(
+ packageName: String,
+ userId: Int,
+ enabled: Boolean,
+ vararg domains: String
+ ) = "pm set-app-links-user-selection --package $packageName --user $userId $enabled " +
+ domains.joinToString(separator = " ")
+}
diff --git a/tests/contentcaptureservice/aidl/Android.bp b/hostsidetests/packagemanager/domainverification/lib/verifications/Android.bp
similarity index 73%
rename from tests/contentcaptureservice/aidl/Android.bp
rename to hostsidetests/packagemanager/domainverification/lib/verifications/Android.bp
index 7e91137..1df2d3e 100644
--- a/tests/contentcaptureservice/aidl/Android.bp
+++ b/hostsidetests/packagemanager/domainverification/lib/verifications/Android.bp
@@ -1,4 +1,3 @@
-//
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,15 +11,18 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-//
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
java_library {
- name: "OutOfPackageDataSharingServiceAidl",
- srcs: [
- "src/**/*.aidl",
+ name: "CtsDomainVerificationVerificationsLibrary",
+ defaults: ["cts_defaults"],
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "compatibility-device-util-axt",
+ "CtsDomainVerificationJavaConstantsLibrary",
+ "truth-prebuilt",
],
}
diff --git a/hostsidetests/packagemanager/domainverification/lib/verifications/src/com/android/cts/packagemanager/verify/domain/SharedVerifications.kt b/hostsidetests/packagemanager/domainverification/lib/verifications/src/com/android/cts/packagemanager/verify/domain/SharedVerifications.kt
new file mode 100644
index 0000000..8cd31e6
--- /dev/null
+++ b/hostsidetests/packagemanager/domainverification/lib/verifications/src/com/android/cts/packagemanager/verify/domain/SharedVerifications.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packagemanager.verify.domain
+
+import android.content.Context
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationUserState
+import com.android.compatibility.common.util.ShellUtils
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DECLARING_PKG_NAME_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_1
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_2
+import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_3
+import com.google.common.truth.Truth
+
+object SharedVerifications {
+
+ fun reset(context: Context) {
+ DomainUtils.ALL_PACKAGES.forEach {
+ ShellUtils.runShellCommand(DomainUtils.setAppLinks(it, "STATE_NO_RESPONSE", "all"))
+ ShellUtils.runShellCommand(DomainUtils.resetAppLinks(it))
+ ShellUtils.runShellCommand(DomainUtils.setAppLinksAllowed(it, context.userId, true))
+ ShellUtils.runShellCommand(
+ DomainUtils.setAppLinksUserSelection(it, context.userId, false, "all")
+ )
+ }
+ }
+
+ fun verifyDomains(context: Context) {
+ val packageName = DECLARING_PKG_NAME_1
+ val user = context.user
+ val userId = context.userId
+ val manager = context.getSystemService(DomainVerificationManager::class.java)!!
+ manager.getDomainVerificationUserState(packageName)!!.also {
+ Truth.assertThat(it.packageName).isEqualTo(packageName)
+ Truth.assertThat(it.isLinkHandlingAllowed).isTrue()
+ Truth.assertThat(it.user).isEqualTo(user)
+ Truth.assertThat(it.hostToStateMap).containsExactlyEntriesIn(
+ mapOf(
+ DOMAIN_1 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+ DOMAIN_2 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+ DOMAIN_3 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+ )
+ )
+ }
+
+ // Try to approve both 1 and 2, but only 1 is marked autoVerify
+ ShellUtils.runShellCommand(
+ DomainUtils.setAppLinks(packageName, "STATE_APPROVED", DOMAIN_1, DOMAIN_2)
+ )
+
+ manager.getDomainVerificationUserState(packageName)!!.also {
+ Truth.assertThat(it.packageName).isEqualTo(packageName)
+ Truth.assertThat(it.isLinkHandlingAllowed).isTrue()
+ Truth.assertThat(it.user).isEqualTo(user)
+ Truth.assertThat(it.hostToStateMap).containsExactlyEntriesIn(
+ mapOf(
+ DOMAIN_1 to DomainVerificationUserState.DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+ DOMAIN_3 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+ )
+ )
+ }
+
+ ShellUtils.runShellCommand(
+ DomainUtils.setAppLinksUserSelection(packageName, userId, true, DOMAIN_1, DOMAIN_2)
+ )
+
+ manager.getDomainVerificationUserState(packageName)!!.also {
+ Truth.assertThat(it.packageName).isEqualTo(packageName)
+ Truth.assertThat(it.isLinkHandlingAllowed).isTrue()
+ Truth.assertThat(it.user).isEqualTo(user)
+ Truth.assertThat(it.hostToStateMap).containsExactlyEntriesIn(
+ mapOf(
+ DOMAIN_1 to DomainVerificationUserState.DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DomainVerificationUserState.DOMAIN_STATE_SELECTED,
+ DOMAIN_3 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+ )
+ )
+ }
+
+ ShellUtils.runShellCommand(DomainUtils.setAppLinksAllowed(packageName, userId, false))
+
+ manager.getDomainVerificationUserState(packageName)!!.also {
+ Truth.assertThat(it.packageName).isEqualTo(packageName)
+ Truth.assertThat(it.isLinkHandlingAllowed).isFalse()
+ Truth.assertThat(it.user).isEqualTo(user)
+ Truth.assertThat(it.hostToStateMap).containsExactlyEntriesIn(
+ mapOf(
+ DOMAIN_1 to DomainVerificationUserState.DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DomainVerificationUserState.DOMAIN_STATE_SELECTED,
+ DOMAIN_3 to DomainVerificationUserState.DOMAIN_STATE_NONE,
+ )
+ )
+ }
+ }
+
+}
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index edd6f7a..c2c0e82 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -47,6 +47,16 @@
test_suites: ["device-tests", "mts-mediaprovider", "cts"],
}
android_test_helper_app {
+ name: "CtsScopedStorageTestAppC30",
+ manifest: "ScopedStorageTestHelper/TestAppC.xml",
+ static_libs: ["cts-scopedstorage-lib"],
+ sdk_version: "test_current",
+ target_sdk_version: "30",
+ srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
+}
+android_test_helper_app {
name: "CtsScopedStorageTestAppCLegacy",
manifest: "ScopedStorageTestHelper/TestAppCLegacy.xml",
static_libs: ["cts-scopedstorage-lib"],
@@ -79,6 +89,34 @@
// Tag as a CTS artifact
test_suites: ["device-tests", "mts-mediaprovider", "cts"],
}
+android_test_helper_app {
+ name: "CtsScopedStorageTestAppFileManagerBypassDB",
+ manifest: "ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml",
+ static_libs: ["cts-scopedstorage-lib"],
+ sdk_version: "test_current",
+ srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
+}
+android_test_helper_app {
+ name: "CtsScopedStorageTestAppSystemGalleryBypassDB",
+ manifest: "ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml",
+ static_libs: ["cts-scopedstorage-lib"],
+ sdk_version: "test_current",
+ srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
+}
+android_test_helper_app {
+ name: "CtsScopedStorageTestAppSystemGallery30BypassDB",
+ manifest: "ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml",
+ static_libs: ["cts-scopedstorage-lib"],
+ sdk_version: "test_current",
+ target_sdk_version: "30",
+ srcs: ["ScopedStorageTestHelper/src/**/*.java"],
+ // Tag as a CTS artifact
+ test_suites: ["device-tests", "mts", "cts"],
+}
android_test_helper_app {
name: "CtsLegacyStorageTestAppRequestLegacy",
@@ -116,6 +154,7 @@
":CtsScopedStorageTestAppCLegacy",
]
}
+
android_test {
name: "LegacyStorageTest",
manifest: "legacy/AndroidManifest.xml",
@@ -131,6 +170,17 @@
]
}
+android_test {
+ name: "SignatureStorageTest",
+ manifest: "signature/AndroidManifest.xml",
+ srcs: ["signature/src/**/*.java"],
+ static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"],
+ compile_multilib: "both",
+ test_suites: ["general-tests", "mts", "cts"],
+ sdk_version: "test_current",
+ certificate: "platform",
+}
+
java_test_host {
name: "CtsScopedStorageCoreHostTest",
srcs: [
@@ -177,8 +227,12 @@
":CtsScopedStorageTestAppA",
":CtsScopedStorageTestAppB",
":CtsScopedStorageTestAppC",
+ ":CtsScopedStorageTestAppC30",
":CtsScopedStorageTestAppCLegacy",
":CtsScopedStorageTestAppDLegacy",
":CtsScopedStorageTestAppFileManager",
+ ":CtsScopedStorageTestAppFileManagerBypassDB",
+ ":CtsScopedStorageTestAppSystemGalleryBypassDB",
+ ":CtsScopedStorageTestAppSystemGallery30BypassDB",
]
}
diff --git a/hostsidetests/scopedstorage/AndroidManifest.xml b/hostsidetests/scopedstorage/AndroidManifest.xml
index da158c3..5886d93 100644
--- a/hostsidetests/scopedstorage/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/AndroidManifest.xml
@@ -20,7 +20,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
- <application>
+ <uses-permission android:name="android.permission.ACCESS_MTP" />
+ <application android:requestOptimizedExternalStorageAccess="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/hostsidetests/scopedstorage/AndroidTest.xml b/hostsidetests/scopedstorage/AndroidTest.xml
index 8749087..560ad48 100644
--- a/hostsidetests/scopedstorage/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/AndroidTest.xml
@@ -23,6 +23,7 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="ScopedStorageTest.apk" />
+ <option name="test-file-name" value="SignatureStorageTest.apk" />
<option name="test-file-name" value="LegacyStorageTest.apk" />
<option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
<option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
diff --git a/hostsidetests/scopedstorage/PublicVolumeTest.xml b/hostsidetests/scopedstorage/PublicVolumeTest.xml
index 1dc4017..e6c1cee 100644
--- a/hostsidetests/scopedstorage/PublicVolumeTest.xml
+++ b/hostsidetests/scopedstorage/PublicVolumeTest.xml
@@ -18,6 +18,7 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="ScopedStorageTest.apk" />
+ <option name="test-file-name" value="SignatureStorageTest.apk" />
<option name="test-file-name" value="LegacyStorageTest.apk" />
<option name="test-file-name" value="CtsScopedStorageTestAppA.apk" />
<option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml
new file mode 100644
index 0000000..b997bc9
--- /dev/null
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.scopedstorage.cts.testapp.filemanagerbypassdb"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
+ <application android:label="TestAppFileManagerBypassDB" android:requestOptimizedExternalStorageAccess="true">
+ <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
+ <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>
+
+ <provider
+ android:name="androidx.core.content.FileProvider"
+ android:authorities="android.scopedstorage.cts.testapp.filemanagerbypassdb"
+ android:exported="false"
+ android:grantUriPermissions="true">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_paths" />
+ </provider>
+ </application>
+</manifest>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml
new file mode 100644
index 0000000..b2dcf6e
--- /dev/null
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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.scopedstorage.cts.testapp.SystemGalleryBypassDB"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+
+ <application android:label="TestAppSystemGalleryBypassDB" android:requestOptimizedExternalStorageAccess="true">
+ <activity android:name="android.scopedstorage.cts.ScopedStorageTestHelper" android:exported="true" >
+ <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>
+
+ <provider
+ android:name="androidx.core.content.FileProvider"
+ android:authorities="android.scopedstorage.cts.testapp.SystemGalleryBypassDB"
+ android:exported="false"
+ android:grantUriPermissions="true">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_paths" />
+ </provider>
+ </application>
+</manifest>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
index 11efea1..4c1e8ec 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
@@ -20,6 +20,7 @@
import static android.scopedstorage.cts.lib.TestUtils.CAN_OPEN_FILE_FOR_READ_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.CAN_OPEN_FILE_FOR_WRITE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.CAN_READ_WRITE_QUERY;
+import static android.scopedstorage.cts.lib.TestUtils.CHECK_DATABASE_ROW_EXISTS_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.CREATE_FILE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.CREATE_IMAGE_ENTRY_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.DELETE_FILE_QUERY;
@@ -30,8 +31,11 @@
import static android.scopedstorage.cts.lib.TestUtils.OPEN_FILE_FOR_WRITE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.QUERY_TYPE;
import static android.scopedstorage.cts.lib.TestUtils.READDIR_QUERY;
+import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_PARAMS_SEPARATOR;
+import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.SETATTR_QUERY;
import static android.scopedstorage.cts.lib.TestUtils.canOpen;
+import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
import android.app.Activity;
@@ -94,6 +98,12 @@
case CREATE_IMAGE_ENTRY_QUERY:
returnIntent = createImageEntry(queryType);
break;
+ case RENAME_FILE_QUERY:
+ returnIntent = renameFile(queryType);
+ break;
+ case CHECK_DATABASE_ROW_EXISTS_QUERY:
+ returnIntent = checkDatabaseRowExists(queryType);
+ break;
case "null":
default:
throw new IllegalStateException(
@@ -214,6 +224,36 @@
}
}
+ private Intent renameFile(String queryType) {
+ if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
+ String[] paths = getIntent().getStringExtra(INTENT_EXTRA_PATH)
+ .split(RENAME_FILE_PARAMS_SEPARATOR);
+ File src = new File(paths[0]);
+ File dst = new File(paths[1]);
+ boolean result = src.renameTo(dst);
+ final Intent intent = new Intent(queryType);
+ intent.putExtra(queryType, result);
+ return intent;
+ } else {
+ throw new IllegalStateException(
+ queryType + ": File paths not set from launcher app");
+ }
+ }
+
+ private Intent checkDatabaseRowExists(String queryType) {
+ if (getIntent().hasExtra(INTENT_EXTRA_PATH)) {
+ final String filePath = getIntent().getStringExtra(INTENT_EXTRA_PATH);
+ boolean result =
+ getFileRowIdFromDatabase(getContentResolver(), new File(filePath)) != -1;
+ final Intent intent = new Intent(queryType);
+ intent.putExtra(queryType, result);
+ return intent;
+ } else {
+ throw new IllegalStateException(
+ queryType + ": File path not set from launcher app");
+ }
+ }
+
private void maybeCreateParentDirInAndroid(File file) {
final String ownedPathType = getOwnedDirectoryType(file);
if (ownedPathType == null) {
diff --git a/hostsidetests/scopedstorage/device/AndroidTest.xml b/hostsidetests/scopedstorage/device/AndroidTest.xml
index fb8d2bc..7e6f895 100644
--- a/hostsidetests/scopedstorage/device/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/device/AndroidTest.xml
@@ -32,4 +32,8 @@
<option name="package" value="android.scopedstorage.cts.device" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
</test>
+
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+ </object>
</configuration>
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/BypassDatabaseOperationsTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/BypassDatabaseOperationsTest.java
new file mode 100644
index 0000000..086a0d4
--- /dev/null
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/BypassDatabaseOperationsTest.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.scopedstorage.cts.device;
+
+import static android.app.AppOpsManager.permissionToOp;
+import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
+import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
+import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
+import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
+import static android.scopedstorage.cts.lib.TestUtils.getPicturesDir;
+import static android.scopedstorage.cts.lib.TestUtils.installApp;
+import static android.scopedstorage.cts.lib.TestUtils.installAppWithStoragePermissions;
+import static android.scopedstorage.cts.lib.TestUtils.renameFileAs;
+import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.provider.MediaStore;
+import android.scopedstorage.cts.lib.TestUtils;
+import android.util.Log;
+
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+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;
+
+/**
+ * Device-side test suite to verify file path operations optionally bypassing database operations.
+ */
+@RunWith(Parameterized.class)
+public class BypassDatabaseOperationsTest extends ScopedStorageBaseDeviceTest {
+ static final String TAG = "BypassDatabaseOperationsTest";
+ // An app with READ_EXTERNAL_STORAGE permission. Targets current SDK and is preinstalled
+ private static final TestApp APP_SYSTEM_GALLERY_DEFAULT = new TestApp("TestAppA",
+ "android.scopedstorage.cts.testapp.A.withres", 1, false,
+ "CtsScopedStorageTestAppA.apk");
+ // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets current SDK and has
+ // requestOptimizedExternalStorageAccess=true
+ private static final TestApp APP_SYSTEM_GALLERY_BYPASS_DB = new TestApp(
+ "TestAppSystemGalleryBypassDB",
+ "android.scopedstorage.cts.testapp.SystemGalleryBypassDB", 1, false,
+ "CtsScopedStorageTestAppSystemGalleryBypassDB.apk");
+ // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets targetSDK=30.
+ private static final TestApp APP_SYSTEM_GALLERY_30 = new TestApp("TestAppC",
+ "android.scopedstorage.cts.testapp.C", 1, false,
+ "CtsScopedStorageTestAppC30.apk");
+ // An app with READ_EXTERNAL_STORAGE_PERMISSION. Targets targetSDK=30 and has
+ // requestOptimizedExternalStorageAccess=true
+ private static final TestApp APP_SYSTEM_GALLERY_30_BYPASS_DB = new TestApp(
+ "TestAppSystemGalleryBypassDB",
+ "android.scopedstorage.cts.testapp.SystemGalleryBypassDB", 1, false,
+ "CtsScopedStorageTestAppSystemGallery30BypassDB.apk");
+ // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission.
+ // Targets current SDK and preinstalled
+ private static final TestApp APP_FM_DEFAULT = new TestApp(
+ "TestAppFileManager", "android.scopedstorage.cts.testapp.filemanager", 1, false,
+ "CtsScopedStorageTestAppFileManager.apk");
+ // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission.
+ // Targets current SDK and has requestOptimizedExternalStorageAccess=true
+ private static final TestApp APP_FM_BYPASS_DATABASE_OPS = new TestApp(
+ "TestAppFileManagerBypassDB", "android.scopedstorage.cts.testapp.filemanagerbypassdb",
+ 1, false, "CtsScopedStorageTestAppFileManagerBypassDB.apk");
+ // An app that has file manager (MANAGE_EXTERNAL_STORAGE) permission and targets targetSDK=30
+ private static final TestApp APP_FM_TARGETS_30 = new TestApp("TestAppC",
+ "android.scopedstorage.cts.testapp.C", 1, false,
+ "CtsScopedStorageTestAppC30.apk");
+
+ private static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
+ permissionToOp(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+ private static final String[] SYSTEM_GALLERY_APPOPS = {
+ AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
+
+ /**
+ * To help avoid flaky tests, give ourselves a unique nonce to be used for
+ * all filesystem paths, so that we don't risk conflicting with previous
+ * test runs.
+ */
+ static final String NONCE = String.valueOf(System.nanoTime());
+
+ static final String IMAGE_FILE_NAME = "BypassDatabaseOperations_file_" + NONCE + ".jpg";
+
+ @BeforeClass
+ public static void setupApps() throws Exception {
+ // File manager needs to be explicitly granted MES app op.
+ final int fmUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_FM_DEFAULT.getPackageName(), 0);
+ allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+ }
+
+ @Parameter(0)
+ public String mVolumeName;
+
+ @Parameters(name = "volume={0}")
+ public static Iterable<? extends Object> data() {
+ return ScopedStorageBaseDeviceTest.getTestParameters();
+ }
+
+ @Before
+ public void setupExternalStorage() {
+ super.setupExternalStorage(mVolumeName);
+ Log.i(TAG, "Using volume : " + mVolumeName);
+ }
+
+
+ /**
+ * Test that app with MANAGE_EXTERNAL_STORAGE permission and targeting
+ * targetSDK=31 or higher will not bypass database operations by default.
+ */
+ @Test
+ public void testManageExternalStorage_DoesntBypassDatabase() throws Exception {
+ testAppDoesntBypassDatabaseOps(APP_FM_DEFAULT);
+ }
+
+ /**
+ * Test that app with MANAGE_EXTERNAL_STORAGE permission, targeting
+ * targetSDK=31 or higher and with requestOptimizedExternalStorageAccess=true
+ * will bypass database operations.
+ */
+ @Test
+ public void testManageExternalStorage_WithBypassFlag_BypassesDatabase() throws Exception {
+ installApp(APP_FM_BYPASS_DATABASE_OPS);
+ try {
+ final int fmUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_FM_BYPASS_DATABASE_OPS.getPackageName(), 0);
+ allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+ testAppBypassesDatabaseOps(APP_FM_BYPASS_DATABASE_OPS);
+ } finally {
+ uninstallAppNoThrow(APP_FM_BYPASS_DATABASE_OPS);
+ }
+ }
+
+ /**
+ * Test that app with MANAGE_EXTERNAL_STORAGE permission and targeting
+ * targetSDK=30 or lower will bypass database operations by default.
+ */
+ @Test
+ public void testManageExternalStorage_targets30_BypassesDatabase() throws Exception {
+ installApp(APP_FM_TARGETS_30);
+ try {
+ final int fmUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_FM_TARGETS_30.getPackageName(), 0);
+ allowAppOpsToUid(fmUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
+ testAppBypassesDatabaseOps(APP_FM_TARGETS_30);
+ } finally {
+ uninstallAppNoThrow(APP_FM_TARGETS_30);
+ }
+ }
+
+ /**
+ * Test that app with SYSTEM_GALLERY role and targeting
+ * targetSDK=current or higher will not bypass database operations by default.
+ */
+ @Test
+ public void testSystemGallery_DoesntBypassDatabase() throws Exception {
+ final int sgUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_SYSTEM_GALLERY_DEFAULT.getPackageName(), 0);
+ try {
+ allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+ testAppDoesntBypassDatabaseOps(APP_SYSTEM_GALLERY_DEFAULT);
+ } finally {
+ denyAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+ }
+ }
+
+
+ /**
+ * Test that app with SYSTEM_GALLERY role, targeting
+ * targetSDK=current or higher and with requestOptimizedSystemGalleryAccess=true
+ * will bypass database operations.
+ */
+ @Test
+ public void testSystemGallery_WithBypassFlag_BypassesDatabase() throws Exception {
+ installAppWithStoragePermissions(APP_SYSTEM_GALLERY_BYPASS_DB);
+ try {
+ final int sgUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_SYSTEM_GALLERY_BYPASS_DB.getPackageName(), 0);
+ allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+ testAppBypassesDatabaseOps(APP_SYSTEM_GALLERY_BYPASS_DB);
+ } finally {
+ uninstallAppNoThrow(APP_SYSTEM_GALLERY_BYPASS_DB);
+ }
+ }
+
+ /**
+ * Test that app with SYSTEM_GALLERY role and targeting
+ * targetSDK=30 or higher will not bypass database operations by default.
+ */
+ @Test
+ public void testSystemGallery_targets30_DoesntBypassDatabase() throws Exception {
+ installAppWithStoragePermissions(APP_SYSTEM_GALLERY_30);
+ try {
+ final int sgUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_SYSTEM_GALLERY_30.getPackageName(), 0);
+ allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+ testAppDoesntBypassDatabaseOps(APP_SYSTEM_GALLERY_30);
+ } finally {
+ uninstallAppNoThrow(APP_SYSTEM_GALLERY_30);
+ }
+ }
+
+ /**
+ * Test that app with SYSTEM_GALLERY role, targeting
+ * targetSDK=30 or higher and with requestOptimizedSystemGalleryAccess=true
+ * will bypass database operations.
+ */
+ @Test
+ public void testSystemGallery_targets30_WithBypassFlag_BypassesDatabase() throws Exception {
+ installAppWithStoragePermissions(APP_SYSTEM_GALLERY_30_BYPASS_DB);
+ try {
+ final int sgUid =
+ getContext().getPackageManager().getPackageUid(
+ APP_SYSTEM_GALLERY_30_BYPASS_DB.getPackageName(), 0);
+ allowAppOpsToUid(sgUid, SYSTEM_GALLERY_APPOPS);
+ testAppBypassesDatabaseOps(APP_SYSTEM_GALLERY_30_BYPASS_DB);
+ } finally {
+ uninstallAppNoThrow(APP_SYSTEM_GALLERY_30_BYPASS_DB);
+ }
+ }
+
+ private void testAppDoesntBypassDatabaseOps(TestApp app) throws Exception {
+ final File file = new File(getDcimDir(), IMAGE_FILE_NAME);
+ final File renamedFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+ try {
+ assertThat(createFileAs(app, file.getAbsolutePath())).isTrue();
+ // File path create() added file to database.
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isTrue();
+
+ assertThat(renameFileAs(app, file, renamedFile)).isTrue();
+ // File path rename() also updates the database row
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isFalse();
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isTrue();
+
+ assertThat(deleteFileAs(app, renamedFile.getAbsolutePath())).isTrue();
+ // File path delete() removes database row.
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isFalse();
+ } finally {
+ if (file.exists()) {
+ deleteFileAsNoThrow(app, file.getAbsolutePath());
+ }
+ if (renamedFile.exists()) {
+ deleteFileAsNoThrow(app, renamedFile.getAbsolutePath());
+ }
+ }
+ }
+
+ private void testAppBypassesDatabaseOps(TestApp app) throws Exception {
+ final File file = new File(getDcimDir(), IMAGE_FILE_NAME);
+ final File renamedFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+ try {
+ assertThat(createFileAs(app, file.getAbsolutePath())).isTrue();
+ // File path create() didn't add the file to database.
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isFalse();
+
+ // Ensure file is added to database.
+ assertNotNull(MediaStore.scanFile(getContentResolver(), file));
+
+ assertThat(renameFileAs(app, file, renamedFile)).isTrue();
+ // Rename() didn't update the database row.
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, file)).isTrue();
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isFalse();
+
+ // Ensure database is updated with renamed path
+ assertNull(MediaStore.scanFile(getContentResolver(), file));
+ assertNotNull(MediaStore.scanFile(getContentResolver(), renamedFile));
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isTrue();
+
+ assertThat(deleteFileAs(app, renamedFile.getAbsolutePath())).isTrue();
+ // Unlink() didn't remove the database row.
+ assertThat(TestUtils.checkDatabaseRowExistsAs(app, renamedFile)).isTrue();
+ } finally {
+ if (file.exists()) {
+ deleteFileAsNoThrow(app, file.getAbsolutePath());
+ }
+ if (renamedFile.exists()) {
+ deleteFileAsNoThrow(app, renamedFile.getAbsolutePath());
+ }
+ MediaStore.scanFile(getContentResolver(), file);
+ MediaStore.scanFile(getContentResolver(), renamedFile);
+ }
+ }
+}
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
index 451e7aa..d3c7ccb 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -109,6 +109,7 @@
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import android.Manifest;
@@ -151,7 +152,9 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -2613,6 +2616,7 @@
@Test
public void testDeferredScanHidesPartialDatabaseRows() throws Exception {
ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.IS_PENDING, 1);
// Insert a pending row
final Uri targetUri = getContentResolver().insert(getImageContentUri(), values, null);
try (InputStream in =
@@ -2660,6 +2664,73 @@
}
}
+ private void testRedactedUriCommon(Uri uri, Uri redactedUri) {
+ assertEquals(redactedUri.getAuthority(), uri.getAuthority());
+ assertEquals(redactedUri.getScheme(), uri.getScheme());
+ assertNotEquals(redactedUri.getPath(), uri.getPath());
+ assertNotEquals(redactedUri.getPathSegments(), uri.getPathSegments());
+
+ final String uriId = redactedUri.getLastPathSegment();
+ assertThat(uriId.startsWith("RUID")).isTrue();
+ assertEquals(uriId.length(), 36);
+ }
+
+ @Test
+ public void testRedactedUri_single() throws Exception {
+ final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME);
+
+ try {
+ final Uri uri = MediaStore.scanFile(getContentResolver(), img);
+ final Uri redactedUri = MediaStore.getRedactedUri(getContentResolver(), uri);
+ testRedactedUriCommon(uri, redactedUri);
+ } finally {
+ img.delete();
+ }
+ }
+
+ @Test
+ public void testRedactedUri_list() throws Exception {
+ List<Uri> uris = new ArrayList<>();
+ List<File> files = new ArrayList<>();
+
+ try {
+ for (int i = 0; i < 10; i++) {
+ File file = stageImageFileWithMetadata("img_metadata" + String.valueOf(
+ System.nanoTime()) + i + ".jpg");
+ files.add(file);
+ uris.add(MediaStore.scanFile(getContentResolver(), file));
+ }
+
+ final Collection<Uri> redactedUris = MediaStore.getRedactedUri(getContentResolver(),
+ uris);
+ int i = 0;
+ for (Uri redactedUri : redactedUris) {
+ Uri uri = uris.get(i++);
+ testRedactedUriCommon(uri, redactedUri);
+ }
+ } finally {
+ files.forEach(file -> file.delete());
+ }
+ }
+
+ private String getStringFromCursor(Cursor c, String colName) {
+ return c.getString(c.getColumnIndex(colName));
+ }
+
+ private File stageImageFileWithMetadata(String name) throws Exception {
+ final File img = new File(
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), name);
+
+ try (InputStream in =
+ getContext().getResources().openRawResource(R.raw.img_with_metadata);
+ OutputStream out = new FileOutputStream(img)) {
+ // Dump the image we have to external storage
+ FileUtils.copy(in, out);
+ }
+
+ return img;
+ }
+
private void assertCanWriteAndRead(File file, byte[] data) throws Exception {
// Assert we can write to images/videos
try (FileOutputStream fos = new FileOutputStream(file)) {
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index db782d1..7eba789 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -59,6 +59,15 @@
.setDisableIsolatedStorage(true));
}
+ /**
+ * Runs the given phase of SignatureStorageTest by calling into the device.
+ * Throws an exception if the test phase fails.
+ */
+ void runDeviceTestWithPlatformSignature(String phase) throws Exception {
+ assertThat(runDeviceTests("android.scopedstorage.cts.signature",
+ "android.scopedstorage.cts.signature.SignatureStorageTest", phase)).isTrue();
+ }
+
private void setupExternalStorage() throws Exception {
if (!mIsExternalStorageSetup) {
runDeviceTest("setupExternalStorage");
@@ -149,6 +158,21 @@
}
@Test
+ public void testMTPAppWithoutPlatformSignatureCannotAccessAndroidDirs() throws Exception {
+ runDeviceTest("testMTPAppWithoutPlatformSignatureCannotAccessAndroidDirs");
+ }
+
+ @Test
+ public void testMTPAppWithPlatformSignatureCanAccessAndroidDirs() throws Exception {
+ runDeviceTestWithPlatformSignature("testMTPAppWithPlatformSignatureCanAccessAndroidDirs");
+ }
+
+ @Test
+ public void testExternalStorageProviderAndDownloadsProvider() throws Exception {
+ runDeviceTest("testExternalStorageProviderAndDownloadsProvider");
+ }
+
+ @Test
public void testManageExternalStorageQueryOtherAppsFile() throws Exception {
allowAppOps("android:manage_external_storage");
try {
@@ -318,14 +342,18 @@
}
}
- private void grantPermissions(String... perms) throws Exception {
+ private void grantPermissionsToPackage(String packageName, String... perms) throws Exception {
int currentUserId = getCurrentUserId();
for (String perm : perms) {
- executeShellCommand("pm grant --user %d android.scopedstorage.cts %s",
- currentUserId, perm);
+ executeShellCommand("pm grant --user %d %s %s",
+ currentUserId, packageName, perm);
}
}
+ private void grantPermissions(String... perms) throws Exception {
+ grantPermissionsToPackage("android.scopedstorage.cts", perms);
+ }
+
private void revokePermissions(String... perms) throws Exception {
int currentUserId = getCurrentUserId();
for (String perm : perms) {
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
index ef8fc14..e12cf78 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
@@ -46,6 +46,7 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
import android.provider.MediaStore;
import android.system.ErrnoException;
import android.system.Os;
@@ -107,6 +108,9 @@
"android.scopedstorage.cts.can_read_and_write";
public static final String READDIR_QUERY = "android.scopedstorage.cts.readdir";
public static final String SETATTR_QUERY = "android.scopedstorage.cts.setattr";
+ public static final String CHECK_DATABASE_ROW_EXISTS_QUERY =
+ "android.scopedstorage.cts.check_database_row_exists";
+ public static final String RENAME_FILE_QUERY = "android.scopedstorage.cts.renamefile";
public static final String STR_DATA1 = "Just some random text";
public static final String STR_DATA2 = "More arbitrary stuff";
@@ -114,6 +118,8 @@
public static final byte[] BYTES_DATA1 = STR_DATA1.getBytes();
public static final byte[] BYTES_DATA2 = STR_DATA2.getBytes();
+ public static final String RENAME_FILE_PARAMS_SEPARATOR = ";";
+
// Root of external storage
private static File sExternalStorageDirectory = Environment.getExternalStorageDirectory();
private static String sStorageVolumeName = MediaStore.VOLUME_EXTERNAL;
@@ -298,6 +304,30 @@
return getResultFromTestApp(testApp, file.getPath(), actionName);
}
+ /**
+ * Makes the given {@code testApp} rename give {@code src} to {@code dst}.
+ *
+ * The method concatenates source and destination paths while sending the request to
+ * {@code testApp}. Hence, {@link TestUtils#RENAME_FILE_PARAMS_SEPARATOR} shouldn't be used
+ * in path names.
+ *
+ * <p>This method drops shell permission identity.
+ */
+ public static boolean renameFileAs(TestApp testApp, File src, File dst) throws Exception {
+ final String paths = String.format("%s%s%s",
+ src.getAbsolutePath(), RENAME_FILE_PARAMS_SEPARATOR, dst.getAbsolutePath());
+ return getResultFromTestApp(testApp, paths, RENAME_FILE_QUERY);
+ }
+
+ /**
+ * Makes the given {@code testApp} check if a database row exists for given {@code file}
+ *
+ * <p>This method drops shell permission identity.
+ */
+ public static boolean checkDatabaseRowExistsAs(TestApp testApp, File file) throws Exception {
+ return getResultFromTestApp(testApp, file.getPath(), CHECK_DATABASE_ROW_EXISTS_QUERY);
+ }
+
public static Uri insertFileFromExternalMedia(boolean useRelative) throws IOException {
ContentValues values = new ContentValues();
String filePath =
@@ -579,8 +609,16 @@
* entry in the database. Returns {@code -1} if file is not found.
*/
public static int getFileRowIdFromDatabase(@NonNull File file) {
+ return getFileRowIdFromDatabase(getContentResolver(), file);
+ }
+
+ /**
+ * Queries given {@link ContentResolver} for a file and returns the corresponding row ID for
+ * its entry in the database. Returns {@code -1} if file is not found.
+ */
+ public static int getFileRowIdFromDatabase(ContentResolver cr, @NonNull File file) {
int id = -1;
- try (Cursor c = queryFile(file, MediaStore.MediaColumns._ID)) {
+ try (Cursor c = queryFile(cr, file, MediaStore.MediaColumns._ID)) {
if (c.moveToFirst()) {
id = c.getInt(0);
}
@@ -624,7 +662,8 @@
*/
@NonNull
public static Cursor queryVideoFile(File file, String... projection) {
- return queryFile(MediaStore.Video.Media.getContentUri(sStorageVolumeName), file,
+ return queryFile(getContentResolver(),
+ MediaStore.Video.Media.getContentUri(sStorageVolumeName), file,
/*includePending*/ true, projection);
}
@@ -634,7 +673,8 @@
*/
@NonNull
public static Cursor queryImageFile(File file, String... projection) {
- return queryFile(MediaStore.Images.Media.getContentUri(sStorageVolumeName), file,
+ return queryFile(getContentResolver(),
+ MediaStore.Images.Media.getContentUri(sStorageVolumeName), file,
/*includePending*/ true, projection);
}
@@ -874,6 +914,54 @@
}
}
+ public static void assertMountMode(String packageName, int uid, int expectedMountMode) {
+ adoptShellPermissionIdentity("android.permission.WRITE_MEDIA_STORAGE");
+ try {
+ final StorageManager storageManager = getContext().getSystemService(
+ StorageManager.class);
+ final int actualMountMode = storageManager.getExternalStorageMountMode(uid,
+ packageName);
+ assertThat(actualMountMode).isEqualTo(expectedMountMode);
+ } finally {
+ dropShellPermissionIdentity();
+ }
+ }
+
+ public static void assertCanAccessPrivateAppAndroidDataDir(boolean canAccess,
+ TestApp testApp, String callingPackage, String fileName) throws Exception {
+ File[] dataDirs = getContext().getExternalFilesDirs(null);
+ canReadWriteFilesInDirs(dataDirs, canAccess, testApp, callingPackage, fileName);
+ }
+
+ public static void assertCanAccessPrivateAppAndroidObbDir(boolean canAccess,
+ TestApp testApp, String callingPackage, String fileName) throws Exception {
+ File[] obbDirs = getContext().getObbDirs();
+ canReadWriteFilesInDirs(obbDirs, canAccess, testApp, callingPackage, fileName);
+ }
+
+ private static void canReadWriteFilesInDirs(File[] dirs, boolean canAccess, TestApp testApp,
+ String callingPackage, String fileName) throws Exception {
+ for (File dir : dirs) {
+ final File otherAppExternalDataDir = new File(dir.getPath().replace(
+ callingPackage, testApp.getPackageName()));
+ final File file = new File(otherAppExternalDataDir, fileName);
+ try {
+ assertThat(file.exists()).isFalse();
+
+ assertThat(createFileAs(testApp, file.getPath())).isTrue();
+ if (canAccess) {
+ assertThat(file.canRead()).isTrue();
+ assertThat(file.canWrite()).isTrue();
+ } else {
+ assertThat(file.canRead()).isFalse();
+ assertThat(file.canWrite()).isFalse();
+ }
+ } finally {
+ deleteFileAsNoThrow(testApp, file.getAbsolutePath());
+ }
+ }
+ }
+
/**
* Polls for external storage to be mounted.
*/
@@ -1282,19 +1370,25 @@
*/
@NonNull
public static Cursor queryFileExcludingPending(@NonNull File file, String... projection) {
- return queryFile(MediaStore.Files.getContentUri(sStorageVolumeName), file,
- /*includePending*/ false, projection);
+ return queryFile(getContentResolver(), MediaStore.Files.getContentUri(sStorageVolumeName),
+ file, /*includePending*/ false, projection);
+ }
+
+ @NonNull
+ public static Cursor queryFile(ContentResolver cr, @NonNull File file, String... projection) {
+ return queryFile(cr, MediaStore.Files.getContentUri(sStorageVolumeName),
+ file, /*includePending*/ true, projection);
}
@NonNull
public static Cursor queryFile(@NonNull File file, String... projection) {
- return queryFile(MediaStore.Files.getContentUri(sStorageVolumeName), file,
- /*includePending*/ true, projection);
+ return queryFile(getContentResolver(), MediaStore.Files.getContentUri(sStorageVolumeName),
+ file, /*includePending*/ true, projection);
}
@NonNull
- private static Cursor queryFile(@NonNull Uri uri, @NonNull File file, boolean includePending,
- String... projection) {
+ private static Cursor queryFile(ContentResolver cr, @NonNull Uri uri, @NonNull File file,
+ boolean includePending, String... projection) {
Bundle queryArgs = new Bundle();
queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
MediaStore.MediaColumns.DATA + " = ?");
@@ -1308,7 +1402,7 @@
queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_PENDING, MediaStore.MATCH_EXCLUDE);
}
- final Cursor c = getContentResolver().query(uri, projection, queryArgs, null);
+ final Cursor c = cr.query(uri, projection, queryArgs, null);
assertThat(c).isNotNull();
return c;
}
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/AndroidManifest.xml b/hostsidetests/scopedstorage/signature/AndroidManifest.xml
similarity index 60%
rename from tests/sensor/sensorratepermission/EventConnectionResampling/AndroidManifest.xml
rename to hostsidetests/scopedstorage/signature/AndroidManifest.xml
index 7fa9859..1f808f8 100644
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/signature/AndroidManifest.xml
@@ -13,21 +13,17 @@
~ WITHOUT 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.sensorratepermission.cts.resampling">
-
- <uses-permission android:name="android.permission.WAKE_LOCK"/>
-
- <uses-sdk android:minSdkVersion="31" android:targetSdkVersion="31"/>
-
- <application android:label="ResamplingTest">
- <uses-library android:name="android.test.runner"/>
+ package="android.scopedstorage.cts.signature" >
+ <uses-permission android:name="android.permission.ACCESS_MTP" />
+ <application>
+ <uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:functionalTest="true"
- android:targetPackage="android.sensorratepermission.cts.resampling"
- android:label="Two apps register listeners simultaneously"/>
+ android:targetPackage="android.scopedstorage.cts.signature"
+ android:label="Signature app tests for scoped storage"/>
+
</manifest>
diff --git a/hostsidetests/scopedstorage/signature/src/android/scopedstorage/cts/signature/SignatureStorageTest.java b/hostsidetests/scopedstorage/signature/src/android/scopedstorage/cts/signature/SignatureStorageTest.java
new file mode 100644
index 0000000..0c29dab
--- /dev/null
+++ b/hostsidetests/scopedstorage/signature/src/android/scopedstorage/cts/signature/SignatureStorageTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.scopedstorage.cts.signature;
+
+import static android.scopedstorage.cts.lib.TestUtils.adoptShellPermissionIdentity;
+import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidDataDir;
+import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidObbDir;
+import static android.scopedstorage.cts.lib.TestUtils.assertMountMode;
+import static android.scopedstorage.cts.lib.TestUtils.dropShellPermissionIdentity;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import android.os.storage.StorageManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.install.lib.TestApp;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs the scoped storage tests as Signature app on primary external storage.
+ *
+ * <p>These tests are also run on a public volume by {@link PublicVolumeTest}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SignatureStorageTest {
+ static final String THIS_PACKAGE_NAME = getContext().getPackageName();
+ // An app with no permissions
+ private static final TestApp APP_B_NO_PERMS = new TestApp("TestAppB",
+ "android.scopedstorage.cts.testapp.B.noperms", 1, false,
+ "CtsScopedStorageTestAppB.apk");
+ /**
+ * To help avoid flaky tests, give ourselves a unique nonce to be used for
+ * all filesystem paths, so that we don't risk conflicting with previous
+ * test runs.
+ */
+ static final String NONCE = String.valueOf(System.nanoTime());
+
+ static final String NONMEDIA_FILE_NAME = "SignatureStorageTest_file_" + NONCE + ".pdf";
+ /**
+ * Test that signature apps with ACCESS_MTP can access app's private directories in
+ * Android/data and Android/obb
+ */
+ @Test
+ @Ignore("b/183377919")
+ public void testMTPAppWithPlatformSignatureCanAccessAndroidDirs() throws Exception {
+ adoptShellPermissionIdentity(android.Manifest.permission.ACCESS_MTP);
+ try {
+ assertCanAccessPrivateAppAndroidDataDir(true /*canAccess*/, APP_B_NO_PERMS,
+ THIS_PACKAGE_NAME, NONMEDIA_FILE_NAME);
+ assertCanAccessPrivateAppAndroidObbDir(true /*canAccess*/, APP_B_NO_PERMS,
+ THIS_PACKAGE_NAME, NONMEDIA_FILE_NAME);
+ final int uid = getContext().getPackageManager().getPackageUid(THIS_PACKAGE_NAME, 0);
+ assertMountMode(THIS_PACKAGE_NAME, uid,
+ StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE);
+ } finally {
+ dropShellPermissionIdentity();
+ }
+ }
+}
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index 3f90dda..48ab8cd 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -18,9 +18,12 @@
import static android.scopedstorage.cts.lib.TestUtils.BYTES_DATA1;
import static android.scopedstorage.cts.lib.TestUtils.adoptShellPermissionIdentity;
+import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidDataDir;
+import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidObbDir;
import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
+import static android.scopedstorage.cts.lib.TestUtils.assertMountMode;
import static android.scopedstorage.cts.lib.TestUtils.assertThrows;
import static android.scopedstorage.cts.lib.TestUtils.canOpen;
import static android.scopedstorage.cts.lib.TestUtils.canReadAndWriteAs;
@@ -73,10 +76,13 @@
import android.Manifest;
import android.app.WallpaperManager;
+import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
import android.platform.test.annotations.AppModeInstant;
+import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.system.ErrnoException;
import android.system.Os;
@@ -154,25 +160,10 @@
*/
@Test
public void testCheckInstallerAppAccessToObbDirs() throws Exception {
- File[] obbDirs = getContext().getObbDirs();
- for (File obbDir : obbDirs) {
- final File otherAppExternalObbDir = new File(obbDir.getPath().replace(
- THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
- final File file = new File(otherAppExternalObbDir, NONMEDIA_FILE_NAME);
- try {
- assertThat(file.exists()).isFalse();
-
- assertThat(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
- assertFileAccess_readWrite(file);
-
- assertThat(file.delete()).isTrue();
- assertThat(file.exists()).isFalse();
- assertThat(file.createNewFile()).isTrue();
- assertThat(file.exists()).isTrue();
- } finally {
- deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
- }
- }
+ assertCanAccessPrivateAppAndroidObbDir(true /*canAccess*/, APP_B_NO_PERMS,
+ THIS_PACKAGE_NAME, NONMEDIA_FILE_NAME);
+ final int uid = getContext().getPackageManager().getPackageUid(THIS_PACKAGE_NAME, 0);
+ assertMountMode(THIS_PACKAGE_NAME, uid, StorageManager.MOUNT_MODE_EXTERNAL_INSTALLER);
}
/**
@@ -180,20 +171,37 @@
*/
@Test
public void testCheckInstallerAppCannotAccessDataDirs() throws Exception {
- File[] dataDirs = getContext().getExternalFilesDirs(null);
- for (File dataDir : dataDirs) {
- final File otherAppExternalDataDir = new File(dataDir.getPath().replace(
- THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
- final File file = new File(otherAppExternalDataDir, NONMEDIA_FILE_NAME);
- try {
- assertThat(file.exists()).isFalse();
+ assertCanAccessPrivateAppAndroidDataDir(false /*canAccess*/, APP_B_NO_PERMS,
+ THIS_PACKAGE_NAME, NONMEDIA_FILE_NAME);
+ final int uid = getContext().getPackageManager().getPackageUid(THIS_PACKAGE_NAME, 0);
+ assertMountMode(THIS_PACKAGE_NAME, uid, StorageManager.MOUNT_MODE_EXTERNAL_INSTALLER);
+ }
- assertThat(createFileAs(APP_B_NO_PERMS, file.getPath())).isTrue();
- assertCannotReadOrWrite(file);
- } finally {
- deleteFileAsNoThrow(APP_B_NO_PERMS, file.getAbsolutePath());
- }
- }
+ @Test
+ public void testMTPAppWithoutPlatformSignatureCannotAccessAndroidDirs() throws Exception {
+ // TODO(b/183377919): Grant ACCESS_MTP permission via Shell
+ assertCanAccessPrivateAppAndroidDataDir(false /*canAccess*/, APP_B_NO_PERMS,
+ THIS_PACKAGE_NAME, NONMEDIA_FILE_NAME);
+ assertCanAccessPrivateAppAndroidObbDir(false /*canAccess*/, APP_B_NO_PERMS,
+ THIS_PACKAGE_NAME, NONMEDIA_FILE_NAME);
+ }
+
+ /**
+ * Test mount modes for ExternalStorageProvider and DownloadsProvider.
+ */
+ @Test
+ public void testExternalStorageProviderAndDownloadsProvider() throws Exception {
+ assertWritableMountModeForProvider(DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY);
+ assertWritableMountModeForProvider(DocumentsContract.DOWNLOADS_PROVIDER_AUTHORITY);
+ }
+
+ private void assertWritableMountModeForProvider(String auth) {
+ final ProviderInfo provider = getContext().getPackageManager()
+ .resolveContentProvider(auth, 0);
+ int uid = provider.applicationInfo.uid;
+ final String packageName = provider.applicationInfo.packageName;
+
+ assertMountMode(packageName, uid, StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE);
}
@Test
diff --git a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
index 6de6044..aab9484 100644
--- a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
+++ b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
@@ -249,6 +249,16 @@
put("mt6873", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
put("MT6853V/TZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
put("MT6853V/TNZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+ put("MT6833V/ZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+ put("MT6833V/NZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+ put("MT6833V/TZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+ put("MT6833V/TNZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+ put("MT6833V/MZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+ put("MT6833V/MNZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+ put("MT6877V/ZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+ put("MT6877V/NZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+ put("MT6877V/TZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
+ put("MT6877V/TNZA", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
put("SDMMAGPIE", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
put("SM6150", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
put("SM7150", new String[]{"CONFIG_HARDEN_BRANCH_PREDICTOR=y"});
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8332/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8332/Android.bp
index bbe6c7b..382d629 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8332/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8332/Android.bp
@@ -14,6 +14,10 @@
* limitations under the License.
*
*/
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_test {
name: "CVE-2016-8332",
defaults: ["cts_hostsidetests_securitybulletin_defaults"],
@@ -24,8 +28,8 @@
cflags: [
"-DCHECK_OVERFLOW",
],
- shared_libs: [
- "libpdfium",
+ static_libs: [
+ "libpdfium-libopenjpeg2",
],
include_dirs: [
"external/pdfium/third_party/libopenjpeg20",
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
index 5dc4590..d643084 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
@@ -20,6 +20,7 @@
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IAbiReceiver;
@@ -68,6 +69,11 @@
private static Map<ITestDevice, String> sTestName = new HashMap<>();
private static Map<ITestDevice, PocPusher> sPocPusher = new HashMap<>();
+ @Option(name = "set-kptr_restrict",
+ description = "If kptr_restrict should be set to 2 after every reboot")
+ private boolean setKptr_restrict = false;
+ private boolean ignoreKernelAddress = false;
+
/**
* Waits for device to be online, marks the most recent boottime of the device
*/
@@ -85,6 +91,17 @@
pocPusher.setDevice(getDevice()).setBuild(getBuild()).setAbi(getAbi());
sPocPusher.put(getDevice(), pocPusher);
+
+ if (setKptr_restrict) {
+ if (getDevice().enableAdbRoot()) {
+ CLog.i("setting kptr_restrict to 2");
+ getDevice().executeShellCommand("echo 2 > /proc/sys/kernel/kptr_restrict");
+ getDevice().disableAdbRoot();
+ } else {
+ // not a rootable device
+ ignoreKernelAddress = true;
+ }
+ }
}
/**
@@ -160,6 +177,7 @@
*/
public void assertNotKernelPointer(Callable<String> getPtrFunction, ITestDevice deviceToReboot)
throws Exception {
+ assumeFalse("Cannot set kptr_restrict to 2, ignoring kptr test.", ignoreKernelAddress);
String ptr = null;
for (int i = 0; i < 4; i++) { // ~0.4% chance of false positive
ptr = getPtrFunction.call();
diff --git a/hostsidetests/statsdatom/Android.bp b/hostsidetests/statsdatom/Android.bp
index 28f3e8d..737ff68 100644
--- a/hostsidetests/statsdatom/Android.bp
+++ b/hostsidetests/statsdatom/Android.bp
@@ -34,6 +34,7 @@
"src/**/memory/*.java",
"src/**/net/*.java",
"src/**/notification/*.java",
+ "src/**/perfetto/*.java",
"src/**/permissionstate/*.java",
"src/**/settingsstats/*.java",
"src/**/statsd/*.java",
diff --git a/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
index 1ffa00a..025c64a 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
+++ b/hostsidetests/statsdatom/apps/statsdapp/AndroidManifest.xml
@@ -23,6 +23,8 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
index b6bd3ef..6f96efd 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
@@ -42,6 +42,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
@@ -55,7 +56,10 @@
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.cts.util.CtsNetUtils;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkSuggestion;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
@@ -84,8 +88,11 @@
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
@@ -207,8 +214,14 @@
APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, 107);
APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_FINE_LOCATION_SOURCE, 108);
APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_COARSE_LOCATION_SOURCE, 109);
+ APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_MANAGE_MEDIA, 110);
+ APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_BLUETOOTH_CONNECT, 111);
}
+ private static boolean sWasVerboseLoggingEnabled;
+ private static boolean sWasScanThrottleEnabled;
+ private static boolean sWasWifiEnabled;
+
@Test
// Start the isolated service, which logs an AppBreadcrumbReported atom, and then exit.
public void testIsolatedProcessService() throws Exception {
@@ -917,9 +930,11 @@
* Bring up and generate some traffic on cellular data connection.
*/
@Test
- public void testGenerateMobileTraffic() throws Exception {
+ public void testGenerateMobileTraffic() throws IllegalStateException {
final Context context = InstrumentationRegistry.getContext();
- doGenerateNetworkTraffic(context, NetworkCapabilities.TRANSPORT_CELLULAR);
+ if (!doGenerateNetworkTraffic(context, NetworkCapabilities.TRANSPORT_CELLULAR)) {
+ throw new IllegalStateException("Mobile network is not available.");
+ }
}
// Constants which are locally used by doGenerateNetworkTraffic.
@@ -927,21 +942,42 @@
private static final String HTTPS_HOST_URL =
"https://connectivitycheck.gstatic.com/generate_204";
- private void doGenerateNetworkTraffic(@NonNull Context context,
- @NetworkCapabilities.Transport int transport) throws InterruptedException {
+ /**
+ * Returns a Network given a request. The return value is null if the requested Network is
+ * unavailable, and the caller is responsible for logging the error.
+ */
+ private Network getNetworkFromRequest(@NonNull Context context,
+ @NonNull NetworkRequest request) {
final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
- final NetworkRequest request = new NetworkRequest.Builder().addCapability(
- NetworkCapabilities.NET_CAPABILITY_INTERNET).addTransportType(transport).build();
final CtsNetUtils.TestNetworkCallback callback = new CtsNetUtils.TestNetworkCallback();
+ ShellIdentityUtils.invokeWithShellPermissions(() -> cm.requestNetwork(request, callback));
+ final Network network;
+ try {
+ network = callback.waitForAvailable();
+ return network;
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Caught an InterruptedException while looking for requested network!");
+ return null;
+ } finally {
+ cm.unregisterNetworkCallback(callback);
+ }
+ }
- // Request network, and make http query when the network is available.
- cm.requestNetwork(request, callback);
-
- // If network is not available, throws IllegalStateException.
- final Network network = callback.waitForAvailable();
+ /**
+ * Attempts to generate traffic on a network for with a given NetworkRequest. Returns true if
+ * successful, and false is unsuccessful.
+ */
+ private boolean doGenerateNetworkTraffic(@NonNull Context context,
+ @NonNull NetworkRequest request) throws IllegalStateException {
+ final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+ final Network network = getNetworkFromRequest(context, request);
if (network == null) {
- throw new IllegalStateException("network "
- + NetworkCapabilities.transportNameOf(transport) + " is not available.");
+ // Caller should log an error.
+ return false;
+ }
+ if(!cm.bindProcessToNetwork(network)) {
+ Log.e(TAG, "bindProcessToNetwork Failed!");
+ throw new IllegalStateException("bindProcessToNetwork failed!");
}
final long startTime = SystemClock.elapsedRealtime();
@@ -952,9 +988,352 @@
} catch (Exception e) {
Log.e(TAG, "exerciseRemoteHost failed in " + (SystemClock.elapsedRealtime()
- startTime) + " ms: " + e);
- } finally {
- cm.unregisterNetworkCallback(callback);
}
+ return true;
+ }
+
+ /**
+ * Generates traffic on a network with a given transport.
+ */
+ private boolean doGenerateNetworkTraffic(@NonNull Context context,
+ @NetworkCapabilities.Transport int transport) throws IllegalStateException {
+ final NetworkRequest request = new NetworkRequest.Builder().addCapability(
+ NetworkCapabilities.NET_CAPABILITY_INTERNET).addTransportType(transport).build();
+ return doGenerateNetworkTraffic(context, request);
+ }
+
+ /**
+ * Generates traffic on a network with a given set of OEM managed network capabilities.
+ */
+ private boolean doGenerateOemManagedNetworkTraffic(@NonNull Context context,
+ List<Integer> capabilities)
+ throws IllegalStateException {
+ final NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder().addCapability(
+ NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ for (final Integer capability : capabilities) {
+ requestBuilder.addCapability(capability);
+ }
+ final NetworkRequest request = requestBuilder.build();
+ return doGenerateNetworkTraffic(context, request);
+ }
+
+ /**
+ * Assembles a String representation of a list of NetworkCapabilities.
+ */
+ private String oemManagedCapabilitiesToString(@NonNull List<Integer> capabilities) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ for (final Integer capability : capabilities) {
+ sb.append(NetworkCapabilities.capabilityNameOf(capability) + ", ");
+ }
+ // Must have been an empty set of capabilities.
+ if (sb.length() < 3) {
+ sb.append("}");
+ return sb.toString();
+ }
+ // Replace the trailing ", " with a "}".
+ sb.replace(sb.length()-2, sb.length()-1, "}");
+ return sb.toString();
+ }
+ /**
+ * Checks if a network with a given set of OEM managed capabilities (OEM_PAID, for example) is
+ * avaialable.
+ */
+ private boolean isOemManagedNetworkAvailable(@NonNull Context context,
+ List<Integer> capabilities) {
+ final NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder().addCapability(
+ NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ for (final Integer capability : capabilities) {
+ requestBuilder.addCapability(capability);
+ }
+ final NetworkRequest request = requestBuilder.build();
+ final Network network = getNetworkFromRequest(context, request);
+ if (network == null) {
+ return false;
+ }
+ // There's an OEM managed network already available, so use that.
+ return true;
+ }
+
+ /**
+ * Callback WiFi scan results, on which we can await().
+ */
+ private static class TestScanResultsCallback extends WifiManager.ScanResultsCallback {
+ private final CountDownLatch mCountDownLatch;
+ public boolean onAvailableCalled = false;
+
+ TestScanResultsCallback(CountDownLatch countDownLatch) {
+ mCountDownLatch = countDownLatch;
+ }
+
+ @Override
+ public void onScanResultsAvailable() {
+ onAvailableCalled = true;
+ mCountDownLatch.countDown();
+ }
+ }
+
+ /**
+ * Searches for saved WiFi networks, and returns a set of in-range saved WiFi networks.
+ */
+ private Set<WifiConfiguration> getAvailableSavedNetworks(@NonNull Context context,
+ @NonNull WifiManager wifiManager) throws IllegalStateException {
+ final Set<WifiConfiguration> availableNetworks = new HashSet<WifiConfiguration>();
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ final TestScanResultsCallback scanCallback = new TestScanResultsCallback(countDownLatch);
+ final List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.getPrivilegedConfiguredNetworks());
+
+ // If there are no saved networks, we can't connect to WiFi.
+ if(savedNetworks.isEmpty()) {
+ throw new IllegalStateException("Device has no saved WiFi networks.");
+ }
+ setUpOemManagedWifi(wifiManager, savedNetworks);
+ wifiManager.registerScanResultsCallback(context.getMainExecutor(), scanCallback);
+ wifiManager.startScan();
+ try {
+ final boolean didFinish = countDownLatch.await(10, TimeUnit.SECONDS);
+ if (!didFinish) {
+ Log.e(TAG, "Failed to get WiFi scan results: operation timed out.");
+ // Empty list.
+ return availableNetworks;
+ }
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Caught InterruptedException while waiting for WiFi scan results!");
+ return availableNetworks;
+ }
+
+ // ScanResult could refer to Bluetooth or WiFi, so it has to be explicitly stated here.
+ final List<android.net.wifi.ScanResult> scanResults = wifiManager.getScanResults();
+ // Search for a saved network in range.
+ for (final WifiConfiguration savedNetwork : savedNetworks) {
+ for (final android.net.wifi.ScanResult scanResult : scanResults) {
+ if (WifiInfo.sanitizeSsid(savedNetwork.SSID).equals(WifiInfo.sanitizeSsid(
+ scanResult.SSID))) {
+ // We found a saved network that's in range.
+ availableNetworks.add(savedNetwork);
+ }
+ }
+ }
+ if(availableNetworks.isEmpty()) {
+ throw new IllegalStateException("No saved networks in range.");
+ }
+ return availableNetworks;
+ }
+
+ /**
+ * Causes WiFi to disconnect and prevents auto-join from reconnecting.
+ */
+ private void disableNetworksAndDisconnectWifi(@NonNull WifiManager wifiManager,
+ @NonNull List<WifiConfiguration> savedNetworks) {
+ // Disable auto-join for our saved networks, and disconnect from them.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> {
+ for (final WifiConfiguration savedNetwork : savedNetworks) {
+ wifiManager.disableNetwork(savedNetwork.networkId);
+ }
+ wifiManager.disconnect();
+ });
+ }
+
+ /**
+ * Puts the system back in the state we found it before setUpOemManagedWifi was called.
+ */
+ private void tearDownOemManagedWifi(@NonNull WifiManager wifiManager) {
+ // Put the system back the way we found it.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setVerboseLoggingEnabled(sWasVerboseLoggingEnabled));
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setWifiEnabled(sWasWifiEnabled));
+ }
+
+ /**
+ * Builds a suggestion based on a saved WiFi network with a given list of OEM managed
+ * capabilities.
+ */
+ private static WifiNetworkSuggestion.Builder createOemManagedSuggestion(
+ @NonNull WifiConfiguration network, List<Integer> capabilities)
+ throws IllegalStateException {
+ final WifiNetworkSuggestion.Builder suggestionBuilder = new WifiNetworkSuggestion.Builder();
+ for (final Integer capability : capabilities) {
+ switch (capability) {
+ case NetworkCapabilities.NET_CAPABILITY_OEM_PAID:
+ suggestionBuilder.setOemPaid(true);
+ break;
+ case NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE:
+ suggestionBuilder.setOemPrivate(true);
+ break;
+ default:
+ throw new IllegalStateException("Unsupported OEM network capability "
+ + NetworkCapabilities.capabilityNameOf(capability));
+ }
+ }
+ suggestionBuilder.setSsid(WifiInfo.sanitizeSsid(network.SSID));
+ if (network.preSharedKey != null) {
+ if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+ suggestionBuilder.setWpa2Passphrase(WifiInfo.sanitizeSsid(network.preSharedKey));
+ } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+ suggestionBuilder.setWpa3Passphrase(WifiInfo.sanitizeSsid(network.preSharedKey));
+ } else {
+ throw new IllegalStateException("Saved network has unsupported security type");
+ }
+ } else if (network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+ suggestionBuilder.setIsEnhancedOpen(true);
+ } else if (!network.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+ throw new IllegalStateException("Saved network has unsupported security type");
+ }
+ suggestionBuilder.setIsHiddenSsid(network.hiddenSSID);
+ return suggestionBuilder;
+ }
+
+ /**
+ * Helper function for testGenerateOemManagedTraffic. Handles bringing up a WiFi network with a
+ * given list of OEM managed capabilities, and generates traffic on said network.
+ */
+ private boolean setWifiOemManagedAndGenerateTraffic(@NonNull Context context,
+ @NonNull WifiManager wifiManager, @NonNull WifiConfiguration network,
+ List<Integer> capabilities) throws IllegalStateException {
+ final WifiNetworkSuggestion.Builder oemSuggestionBuilder =
+ createOemManagedSuggestion(network, capabilities);
+ final WifiNetworkSuggestion oemSuggestion = oemSuggestionBuilder.build();
+
+ final int status = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.addNetworkSuggestions(Arrays.asList(oemSuggestion)));
+ if (status != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
+ throw new IllegalStateException("Adding WifiNetworkSuggestion failed with "
+ + status);
+ }
+
+ // Wait for the suggestion to go through.
+ CountDownLatch suggestionLatch = new CountDownLatch(1);
+ BroadcastReceiver receiver =
+ registerReceiver(context, suggestionLatch, new IntentFilter(
+ wifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION));
+ PendingIntent pendingSuggestionIntent = PendingIntent.getBroadcast(context, 0,
+ new Intent(wifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION),
+ PendingIntent.FLAG_IMMUTABLE);
+ waitForReceiver(context, 1_000, suggestionLatch, receiver);
+
+ if (isOemManagedNetworkAvailable(context, capabilities)) {
+ if (!doGenerateOemManagedNetworkTraffic(context, capabilities)) {
+ throw new IllegalStateException("Network with "
+ + oemManagedCapabilitiesToString(capabilities) + " is not available.");
+ }
+ } else {
+ // Network didn't come up.
+ Log.e(TAG, "isOemManagedNetworkAvailable reported " + network.SSID + " with "
+ + oemManagedCapabilitiesToString(capabilities) + " is unavailable");
+ wifiManager.removeNetworkSuggestions(Arrays.asList(oemSuggestion));
+ return false;
+ }
+ wifiManager.removeNetworkSuggestions(Arrays.asList(oemSuggestion));
+ return true;
+ }
+
+ /**
+ * Does pre-requisite setup steps for testGenerateOemManagedTraffic via WiFi.
+ */
+ private void setUpOemManagedWifi(@NonNull WifiManager wifiManager,
+ @NonNull List<WifiConfiguration> savedNetworks) {
+ // Get the state the system was in before so we can put it back how we found it.
+ sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.isWifiEnabled());
+ sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.isVerboseLoggingEnabled());
+ sWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.isScanThrottleEnabled());
+
+ // Turn on the wifi.
+ if (!wifiManager.isWifiEnabled()) {
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setWifiEnabled(true));
+ }
+
+ // Enable logging and disable scan throttling.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setVerboseLoggingEnabled(true));
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> wifiManager.setScanThrottleEnabled(false));
+
+ disableNetworksAndDisconnectWifi(wifiManager, savedNetworks);
+ }
+
+ /**
+ * Brings up WiFi networks with specified combinations of OEM managed network capabilities and
+ * generates traffic on said networks.
+ */
+ private void generateWifiOemManagedTraffic(Context context,
+ List<List<Integer>> untestedCapabilities) {
+ final PackageManager packageManager = context.getPackageManager();
+ final WifiManager wifiManager = context.getSystemService(WifiManager.class);
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
+ // If wifi isn't supported, don't bother trying.
+ Log.w(TAG, "Feature WiFi is unavailable!");
+ return;
+ }
+ final Set<WifiConfiguration> availableNetworks = getAvailableSavedNetworks(context,
+ wifiManager);
+
+ boolean foundGoodNetwork = false;
+ // Try to connect to a saved network in range.
+ for (final WifiConfiguration network : availableNetworks) {
+ // Try each of the OEM network capabilities.
+ for (final List<Integer> untestedCapability : untestedCapabilities) {
+ boolean generatedTraffic = setWifiOemManagedAndGenerateTraffic(context, wifiManager,
+ network, untestedCapability);
+ if (foundGoodNetwork && !generatedTraffic) {
+ // This already worked for a prior capability, so something is wrong.
+ Log.e(TAG, network.SSID + " failed to come up with "
+ + oemManagedCapabilitiesToString(untestedCapability));
+ disableNetworksAndDisconnectWifi(wifiManager, Arrays.asList(network));
+ tearDownOemManagedWifi(wifiManager);
+ throw new IllegalStateException(network.SSID + " failed to come up!");
+ } else if (!generatedTraffic) {
+ // This network didn't work, try another one.
+ break;
+ }
+ foundGoodNetwork = true;
+ disableNetworksAndDisconnectWifi(wifiManager, Arrays.asList(network));
+ }
+ if (foundGoodNetwork) {
+ break;
+ }
+ }
+ tearDownOemManagedWifi(wifiManager);
+ if (foundGoodNetwork == false) {
+ throw new IllegalStateException("Couldn't connect to a good WiFi network!");
+ }
+ }
+
+ /**
+ * Bring up and generate some traffic on OEM managed WiFi network.
+ */
+ @Test
+ public void testGenerateOemManagedTraffic() throws Exception {
+ final Context context = InstrumentationRegistry.getContext();
+
+ final List<List<Integer>> oemCapabilities = new LinkedList<>(List.of(
+ List.of(NetworkCapabilities.NET_CAPABILITY_OEM_PAID),
+ List.of(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE),
+ List.of(NetworkCapabilities.NET_CAPABILITY_OEM_PAID,
+ NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE)));
+ final List<List<Integer>> untestedCapabilities = new LinkedList<>(oemCapabilities);
+
+ // In the event an OEM network exists already, use that to test.
+ for (final List<Integer> oemCapability : oemCapabilities) {
+ if (isOemManagedNetworkAvailable(context, oemCapability)) {
+ doGenerateOemManagedNetworkTraffic(context,
+ oemCapability);
+ // Don't try to test on WiFi if the network already exists.
+ untestedCapabilities.remove(oemCapability);
+ }
+ }
+ if (untestedCapabilities.isEmpty()) return;
+
+ // There are capabilities we still need to test, so use WiFi to simulate it.
+ generateWifiOemManagedTraffic(context, untestedCapabilities);
}
/**
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/GnssPowerStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/GnssPowerStatsTests.java
new file mode 100644
index 0000000..47f1905
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/GnssPowerStatsTests.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.statsdatom.gnss;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.os.AtomsProto;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public class GnssPowerStatsTests extends DeviceTestCase implements IBuildReceiver {
+ private static final boolean OPTIONAL_TESTS_ENABLED = true;
+
+ private IBuildInfo mCtsBuild;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ assertThat(mCtsBuild).isNotNull();
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ DeviceUtils.uninstallStatsdTestApp(getDevice());
+ super.tearDown();
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ public void testGnssPowerStats() throws Exception {
+ if (!OPTIONAL_TESTS_ENABLED) return;
+
+ ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.GNSS_POWER_STATS_FIELD_NUMBER);
+
+ AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ List<AtomsProto.Atom> dataList = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+ for (AtomsProto.Atom atom : dataList) {
+ assertThat(atom.getGnssPowerStats().getElapsedRealtimeUncertaintyNanos())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getTotalEnergyMicroJoule()).isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getSinglebandTrackingModeEnergyMicroJoule())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getMultibandTrackingModeEnergyMicroJoule())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getSinglebandAcquisitionModeEnergyMicroJoule())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getMultibandAcquisitionModeEnergyMicroJoule())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule0())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule1())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule2())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule3())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule4())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule5())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule6())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule7())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule8())
+ .isAtLeast(0L);
+ assertThat(atom.getGnssPowerStats().getVendorSpecificPowerModesEnergyMicroJoule9())
+ .isAtLeast(0L);
+ }
+ }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
index 8e4ca4d..3bd8a77 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
@@ -35,7 +35,9 @@
import java.util.List;
public class BytesTransferredTest extends DeviceTestCase implements IBuildReceiver {
+ private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+ private static final String FEATURE_WIFI = "android.hardware.wifi";
private IBuildInfo mCtsBuild;
@@ -124,20 +126,43 @@
);
}
+ public void testOemManagedBytesTransfer() throws Throwable {
+ doTestOemManagedBytesTransferThat(Atom.OEM_MANAGED_BYTES_TRANSFER_FIELD_NUMBER, true,
+ (atom) -> {
+ final AtomsProto.OemManagedBytesTransfer data =
+ atom.getOemManagedBytesTransfer();
+ return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+ data.getRxPackets(), data.getTxPackets(), data.getUid(),
+ data.getOemManagedType(), data.getTransportType());
+ }
+ );
+ }
+
private static class TransferredBytes {
final long mRxBytes;
final long mTxBytes;
final long mRxPackets;
final long mTxPackets;
final long mAppUid;
+ final long mOemManagedType;
+ final long mTransportType;
public TransferredBytes(
long rxBytes, long txBytes, long rxPackets, long txPackets, long appUid) {
+ this(rxBytes, txBytes, rxPackets, txPackets, appUid, /*oemManagedType=*/-1,
+ /*transportType=*/-1);
+ }
+
+ public TransferredBytes(
+ long rxBytes, long txBytes, long rxPackets, long txPackets, long appUid,
+ long oemManagedType, long transportType) {
mRxBytes = rxBytes;
mTxBytes = txBytes;
mRxPackets = rxPackets;
mTxPackets = txPackets;
mAppUid = appUid;
+ mOemManagedType = oemManagedType;
+ mTransportType = transportType;
}
}
@@ -223,6 +248,67 @@
+ " is not found in " + atoms.size() + " atoms.").that(foundAppStats).isTrue();
}
+ private void doTestOemManagedBytesTransferThat(int atomId, boolean isUidAtom,
+ ThrowingPredicate<Atom, Exception> p)
+ throws Throwable {
+ /* PANS is for automotive platforms only, and this test relies on WiFi to simulate OEM
+ * managed networks. */
+ if (!DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)
+ || !DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return;
+
+ // Upload the config.
+ final StatsdConfig.Builder config = ConfigUtils.createConfigBuilder(
+ DeviceUtils.STATSD_ATOM_TEST_PKG);
+ if (isUidAtom) {
+ ConfigUtils.addGaugeMetricForUidAtom(config, atomId, /*uidInAttributionChain=*/false,
+ DeviceUtils.STATSD_ATOM_TEST_PKG);
+ } else {
+ ConfigUtils.addGaugeMetric(config, atomId);
+ }
+ ConfigUtils.uploadConfig(getDevice(), config);
+ // Generate some mobile traffic.
+ DeviceUtils.runDeviceTests(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, ".AtomTests",
+ "testGenerateOemManagedTraffic");
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+ // Force poll NetworkStatsService to get most updated network stats from lower layer.
+ DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ "PollNetworkStatsActivity",
+ /*actionKey=*/null, /*actionValue=*/null);
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+ // Trigger atom pull.
+ AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+ final List<Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice(),
+ /*checkTimestampTruncated=*/false);
+
+ assertThat(atoms.size()).isAtLeast(2);
+ boolean foundAppStats = false;
+ for (final Atom atom : atoms) {
+ TransferredBytes transferredBytes = p.accept(atom);
+ if (transferredBytes != null) {
+ foundAppStats = true;
+ // Checks that the uid in the atom corresponds to the app uid and checks that the
+ // bytes and packet data are as expected.
+ if (isUidAtom) {
+ final int appUid = DeviceUtils.getAppUid(getDevice(),
+ DeviceUtils.STATSD_ATOM_TEST_PKG);
+ assertThat(transferredBytes.mAppUid).isEqualTo(appUid);
+ }
+ assertDataUsageAtomDataExpected(
+ transferredBytes.mRxBytes, transferredBytes.mTxBytes,
+ transferredBytes.mRxPackets, transferredBytes.mTxPackets);
+
+ // Make sure we have a value for the OEM managed type.
+ assertThat(transferredBytes.mOemManagedType).isGreaterThan(0);
+ // Make sure there's a NetworkTemplate#MATCH_* value for transport_type.
+ assertThat(transferredBytes.mTransportType).isGreaterThan(0);
+ }
+ }
+ assertWithMessage("Data for uid " + DeviceUtils.getAppUid(getDevice(),
+ DeviceUtils.STATSD_ATOM_TEST_PKG)
+ + " is not found in " + atoms.size() + " atoms.").that(foundAppStats).isTrue();
+ }
+
private void assertDataUsageAtomDataExpected(long rxb, long txb, long rxp, long txp) {
assertThat(rxb).isGreaterThan(0L);
assertThat(txb).isGreaterThan(0L);
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS
index f78f90b..913ef27 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/OWNERS
@@ -1,4 +1,5 @@
# These atom tests are co-owned by statsd and network team
+chrisweir@google.com
jchalard@google.com
jeffreyhuang@google.com
jtnguyen@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/OWNERS
new file mode 100644
index 0000000..6458574
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/OWNERS
@@ -0,0 +1,6 @@
+# These atom tests are owned by the Perfetto team.
+lalitm@google.com
+hjd@google.com
+primiano@google.com
+fmayer@google.com
+rsavitski@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/PerfettoTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/PerfettoTests.java
new file mode 100644
index 0000000..3a1871f
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/PerfettoTests.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.statsdatom.perfetto;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.internal.os.StatsdConfigProto.Alert;
+import com.android.internal.os.StatsdConfigProto.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.PerfettoDetails;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+import com.android.internal.os.StatsdConfigProto.Subscription;
+import com.android.internal.os.StatsdConfigProto.TimeUnit;
+import com.android.internal.os.StatsdConfigProto.ValueMetric;
+import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.AppBreadcrumbReported;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.PerfettoTrigger;
+import com.android.os.AtomsProto.PerfettoUploaded;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import com.google.protobuf.ByteString;
+
+import perfetto.protos.PerfettoConfig.DataSourceConfig;
+import perfetto.protos.PerfettoConfig.FtraceConfig;
+import perfetto.protos.PerfettoConfig.TraceConfig;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class PerfettoTests extends DeviceTestCase implements IBuildReceiver {
+
+ private static final int WAIT_AFTER_START_PERFETTO_MS = 2000;
+
+ // Config constants
+ // These were chosen to match the statsd <-> Perfetto CTS integration
+ // test.
+ private static final int APP_BREADCRUMB_REPORTED_MATCH_START_ID = 1;
+ private static final int METRIC_ID = 8;
+ private static final int ALERT_ID = 11;
+ private static final int SUBSCRIPTION_ID_PERFETTO = 42;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ super.tearDown();
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ }
+
+ public void testPerfettoUploadedAtoms() throws Exception {
+ if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
+ resetPerfettoGuardrails();
+
+ StatsdConfig.Builder config = getStatsdConfig();
+ ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_UPLOADED_FIELD_NUMBER);
+ ConfigUtils.uploadConfig(getDevice(), config);
+
+ startPerfettoTrace();
+ Thread.sleep(WAIT_AFTER_START_PERFETTO_MS);
+
+ // While the trace would not have finished in this time, we expect at least
+ // the trace to have been started.
+ List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ assertThat(extractPerfettoUploadedEvents(data))
+ .containsAtLeast(
+ PerfettoUploaded.Event.PERFETTO_TRACE_BEGIN,
+ PerfettoUploaded.Event.PERFETTO_ON_CONNECT,
+ PerfettoUploaded.Event.PERFETTO_TRACED_ENABLE_TRACING,
+ PerfettoUploaded.Event.PERFETTO_TRACED_START_TRACING);
+ }
+
+ public void testPerfettoTriggerAtoms() throws Exception {
+ if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
+
+ StatsdConfig.Builder config = ConfigUtils.createConfigBuilder("AID_SHELL");
+ ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_TRIGGER_FIELD_NUMBER);
+ ConfigUtils.uploadConfig(getDevice(), config);
+
+ runTriggerPerfetto();
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ assertThat(data).hasSize(1);
+ assertThat(extractPerfettoTriggerEvents(data))
+ .containsExactly(
+ PerfettoTrigger.Event.PERFETTO_TRIGGER_PERFETTO_TRIGGER);
+ }
+
+ /**
+ * Returns a protobuf-encoded perfetto config that enables the kernel ftrace tracer with
+ * sched_switch for 10 seconds.
+ */
+ private ByteString getPerfettoConfig() {
+ TraceConfig.Builder builder = TraceConfig.newBuilder();
+
+ TraceConfig.BufferConfig buffer =
+ TraceConfig.BufferConfig.newBuilder().setSizeKb(128).build();
+ builder.addBuffers(buffer);
+
+ FtraceConfig ftraceConfig =
+ FtraceConfig.newBuilder().addFtraceEvents("sched/sched_switch").build();
+ DataSourceConfig dataSourceConfig =
+ DataSourceConfig.newBuilder()
+ .setName("linux.ftrace")
+ .setTargetBuffer(0)
+ .setFtraceConfig(ftraceConfig)
+ .build();
+ TraceConfig.DataSource dataSource =
+ TraceConfig.DataSource.newBuilder().setConfig(dataSourceConfig).build();
+ builder.addDataSources(dataSource);
+
+ builder.setDurationMs(3000);
+ builder.setAllowUserBuildTracing(true);
+
+ TraceConfig.IncidentReportConfig incident =
+ TraceConfig.IncidentReportConfig.newBuilder()
+ .setDestinationPackage("foo.bar.baz")
+ .build();
+ builder.setIncidentReportConfig(incident);
+
+ // To avoid being hit with guardrails firing in multiple test runs back
+ // to back, we set a unique session key for each config.
+ Random random = new Random();
+ StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-atom-");
+ sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
+ builder.setUniqueSessionName(sessionNameBuilder.toString());
+
+ return builder.build().toByteString();
+ }
+
+ private List<PerfettoUploaded.Event> extractPerfettoUploadedEvents(
+ List<EventMetricData> input) {
+ List<PerfettoUploaded.Event> output = new ArrayList<>();
+ for (EventMetricData data : input) {
+ output.add(data.getAtom().getPerfettoUploaded().getEvent());
+ }
+ return output;
+ }
+
+ private List<PerfettoTrigger.Event> extractPerfettoTriggerEvents(
+ List<EventMetricData> input) {
+ List<PerfettoTrigger.Event> output = new ArrayList<>();
+ for (EventMetricData data : input) {
+ output.add(data.getAtom().getPerfettoTrigger().getEvent());
+ }
+ return output;
+ }
+
+ /**
+ * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's run too
+ * close of for too many times and hits the upload limit.
+ */
+ private void runTriggerPerfetto() throws Exception {
+ final String cmd = "trigger_perfetto cts.test.trigger";
+ CommandResult cr = getDevice().executeShellV2Command(cmd);
+ if (cr.getStatus() != CommandStatus.SUCCESS)
+ throw new Exception(
+ String.format(
+ "Error while executing %s: %s %s",
+ cmd, cr.getStdout(), cr.getStderr()));
+ }
+
+ /**
+ * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's run too
+ * close of for too many times and hits the upload limit.
+ */
+ private void resetPerfettoGuardrails() throws Exception {
+ final String cmd = "perfetto --reset-guardrails";
+ CommandResult cr = getDevice().executeShellV2Command(cmd);
+ if (cr.getStatus() != CommandStatus.SUCCESS)
+ throw new Exception(
+ String.format(
+ "Error while executing %s: %s %s",
+ cmd, cr.getStdout(), cr.getStderr()));
+ }
+
+ private void startPerfettoTrace() throws Exception {
+ getDevice()
+ .executeShellCommand(
+ String.format(
+ "cmd stats log-app-breadcrumb %d %d",
+ 1, AppBreadcrumbReported.State.START.ordinal()));
+ }
+
+ private final StatsdConfig.Builder getStatsdConfig() throws Exception {
+ return ConfigUtils.createConfigBuilder("AID_NOBODY")
+ .addSubscription(
+ Subscription.newBuilder()
+ .setId(SUBSCRIPTION_ID_PERFETTO)
+ .setRuleType(Subscription.RuleType.ALERT)
+ .setRuleId(ALERT_ID)
+ .setPerfettoDetails(
+ PerfettoDetails.newBuilder()
+ .setTraceConfig(getPerfettoConfig())))
+ .addValueMetric(
+ ValueMetric.newBuilder()
+ .setId(METRIC_ID)
+ .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
+ .setBucket(TimeUnit.ONE_MINUTE)
+ // Get the label field's value:
+ .setValueField(
+ FieldMatcher.newBuilder()
+ .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+ .addChild(
+ FieldMatcher.newBuilder()
+ .setField(
+ AppBreadcrumbReported
+ .LABEL_FIELD_NUMBER))))
+ .addAtomMatcher(
+ StatsdConfigProto.AtomMatcher.newBuilder()
+ .setId(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
+ .setSimpleAtomMatcher(
+ StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+ .setAtomId(
+ Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
+ .addFieldValueMatcher(
+ ConfigUtils.createFvm(
+ AppBreadcrumbReported
+ .STATE_FIELD_NUMBER)
+ .setEqInt(
+ AppBreadcrumbReported.State
+ .START
+ .ordinal()))))
+ .addAlert(
+ Alert.newBuilder()
+ .setId(ALERT_ID)
+ .setMetricId(METRIC_ID)
+ .setNumBuckets(4)
+ .setRefractoryPeriodSecs(0)
+ .setTriggerIfSumGt(0))
+ .addNoReportMetric(METRIC_ID);
+ }
+}
diff --git a/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java
index 6261de1..51dedf2 100644
--- a/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java
+++ b/hostsidetests/tagging/sdk_30/src/android/cts/tagging/sdk30/TaggingTest.java
@@ -73,6 +73,7 @@
mContext.startActivity(intent);
assertTrue(receiver.await());
+ assertTrue(Utils.mistaggedKernelUaccessFails());
}
@Test
@@ -90,7 +91,7 @@
mContext.startActivity(intent);
assertTrue(receiver.await());
- assertTrue(Utils.mistaggedKernelUaccessFails());
+ assertFalse(Utils.mistaggedKernelUaccessFails());
}
@Test
diff --git a/tests/BlobStore/OWNERS b/tests/BlobStore/OWNERS
index bf870975..3a47c48 100644
--- a/tests/BlobStore/OWNERS
+++ b/tests/BlobStore/OWNERS
@@ -1,2 +1,2 @@
-# Bug component: 95221
+# Bug component: 533114
include platform/frameworks/base:/apex/blobstore/OWNERS
diff --git a/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java b/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java
index 3b6c7da..879286f 100644
--- a/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java
+++ b/tests/DropBoxManager/src/android/dropboxmanager/cts/DropBoxTests.java
@@ -133,12 +133,18 @@
restoreDropboxDefaults();
}
- private void sendExcessiveDropBoxEntries(String tag, int count, long delayPerEntry)
+ private void sendExcessiveDropBoxEntries(String tag, int count, long interval)
throws Exception {
+ // addText() can take dozens of milliseconds. In order to ensure addText is called at the
+ // given interval, we keep track of the timestamp when the next addText call should occur.
+ long nextTime = SystemClock.elapsedRealtime();
int i = 0;
mDropBoxManager.addText(tag, String.valueOf(i++));
for (; i < count; i++) {
- Thread.sleep(delayPerEntry);
+ nextTime += interval;
+ // Sleep until when we should send the next entry.
+ final long delay = nextTime - SystemClock.elapsedRealtime();
+ if (delay > 0) Thread.sleep(delay);
mDropBoxManager.addText(tag, String.valueOf(i));
}
}
diff --git a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
index 8c188fc..9fa8601 100644
--- a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
@@ -268,7 +268,7 @@
@Override
public boolean onStopJob(JobParameters params) {
Log.i(TAG, "Received stop callback");
- TestEnvironment.getTestEnvironment().notifyStopped();
+ TestEnvironment.getTestEnvironment().notifyStopped(params);
return mWaitingForStop;
}
@@ -379,6 +379,7 @@
private int mExecutedPermCheckWrite;
private ArrayList<JobWorkItem> mExecutedReceivedWork;
private String mExecutedErrorMessage;
+ private JobParameters mStopJobParameters;
public static TestEnvironment getTestEnvironment() {
if (kTestEnvironment == null) {
@@ -391,10 +392,14 @@
return mExpectedWork;
}
- public JobParameters getLastJobParameters() {
+ public JobParameters getLastStartJobParameters() {
return mExecutedJobParameters;
}
+ public JobParameters getLastStopJobParameters() {
+ return mStopJobParameters;
+ }
+
public int getLastPermCheckRead() {
return mExecutedPermCheckRead;
}
@@ -476,7 +481,8 @@
mWaitingForStopLatch.countDown();
}
- private void notifyStopped() {
+ private void notifyStopped(JobParameters params) {
+ mStopJobParameters = params;
if (mStoppedLatch != null) {
mStoppedLatch.countDown();
}
@@ -536,6 +542,7 @@
public void setUp() {
mLatch = null;
mExecutedJobParameters = null;
+ mStopJobParameters = null;
}
}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
index 035247d..015fe51 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
@@ -17,9 +17,12 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static com.android.compatibility.common.util.TestUtils.waitUntil;
+import android.Manifest;
import android.annotation.TargetApi;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -52,6 +55,9 @@
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Schedules jobs with the {@link android.app.job.JobScheduler} that have network connectivity
@@ -82,6 +88,9 @@
private boolean mHasTelephony;
/** Track whether WiFi was enabled in case we turn it off. */
private boolean mInitialWiFiState;
+ /** Track initial WiFi metered state. */
+ private String mInitialWiFiMeteredState;
+ private String mInitialWiFiSSID;
/** Track whether restrict background policy was enabled in case we turn it off. */
private boolean mInitialRestrictBackground;
/** Track whether airplane mode was enabled in case we toggle it. */
@@ -108,6 +117,9 @@
if (mHasWifi) {
mInitialWiFiState = mWifiManager.isWifiEnabled();
ensureSavedWifiNetwork(mWifiManager);
+ setWifiState(true, mCm, mWifiManager);
+ mInitialWiFiSSID = getWifiSSID();
+ mInitialWiFiMeteredState = getWifiMeteredStatus(mInitialWiFiSSID);
}
mInitialRestrictBackground = SystemUtil
.runShellCommand(getInstrumentation(), RESTRICT_BACKGROUND_GET_CMD)
@@ -139,12 +151,15 @@
Settings.Global.ENABLE_RESTRICTED_BUCKET, mInitialRestrictedBucketEnabled);
// Ensure that we leave WiFi in its previous state.
- if (mHasWifi && mWifiManager.isWifiEnabled() != mInitialWiFiState) {
- try {
- setWifiState(mInitialWiFiState, mCm, mWifiManager);
- } catch (AssertionFailedError e) {
- // Don't fail the test just because wifi state wasn't set in tearDown.
- Log.e(TAG, "Failed to return wifi state to " + mInitialWiFiState, e);
+ if (mHasWifi) {
+ setMeteredState(mInitialWiFiSSID, mInitialWiFiMeteredState);
+ if (mWifiManager.isWifiEnabled() != mInitialWiFiState) {
+ try {
+ setWifiState(mInitialWiFiState, mCm, mWifiManager);
+ } catch (AssertionFailedError e) {
+ // Don't fail the test just because wifi state wasn't set in tearDown.
+ Log.e(TAG, "Failed to return wifi state to " + mInitialWiFiState, e);
+ }
}
}
@@ -164,7 +179,7 @@
Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
return;
}
- connectToWifi();
+ setMeteredState(false);
kTestEnvironment.setExpectedExecutions(1);
mJobScheduler.schedule(
@@ -185,7 +200,7 @@
Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
return;
}
- connectToWifi();
+ setMeteredState(false);
kTestEnvironment.setExpectedExecutions(1);
mJobScheduler.schedule(
@@ -207,7 +222,7 @@
Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
return;
}
- connectToWifi();
+ setMeteredState(false);
setDataSaverEnabled(true);
kTestEnvironment.setExpectedExecutions(1);
@@ -217,7 +232,7 @@
runSatisfiedJob(CONNECTIVITY_JOB_ID);
- assertTrue("Job with connectivity constraint did not fire on WiFi.",
+ assertTrue("Job with connectivity constraint did not fire on unmetered WiFi.",
kTestEnvironment.awaitExecution());
}
@@ -243,6 +258,26 @@
}
/**
+ * Schedule a job with a generic connectivity constraint, and ensure that it executes
+ * on a metered wifi connection.
+ */
+ public void testConnectivityConstraintExecutes_withMeteredWifi() throws Exception {
+ if (!mHasWifi) {
+ return;
+ }
+ setMeteredState(true);
+
+ kTestEnvironment.setExpectedExecutions(1);
+ mJobScheduler.schedule(
+ mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build());
+
+ runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+ assertTrue("Job with connectivity constraint did not fire on metered wifi.",
+ kTestEnvironment.awaitExecution());
+ }
+
+ /**
* Schedule a job with a generic connectivity constraint, and ensure that it isn't stopped when
* the device transitions to WiFi.
*/
@@ -277,7 +312,7 @@
* Schedule a job with a metered connectivity constraint, and ensure that it executes
* on a mobile data connection.
*/
- public void testConnectivityConstraintExecutes_metered() throws Exception {
+ public void testConnectivityConstraintExecutes_metered_mobile() throws Exception {
if (!checkDeviceSupportsMobileData()) {
return;
}
@@ -294,22 +329,48 @@
}
/**
+ * Schedule a job with a metered connectivity constraint, and ensure that it executes
+ * on a mobile data connection.
+ */
+ public void testConnectivityConstraintExecutes_metered_Wifi() throws Exception {
+ if (!mHasWifi) {
+ return;
+ }
+ setMeteredState(true);
+
+
+ kTestEnvironment.setExpectedExecutions(1);
+ mJobScheduler.schedule(
+ mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED).build());
+
+ // Since we equate "metered" to "cellular", the job shouldn't start.
+ runSatisfiedJob(CONNECTIVITY_JOB_ID);
+ assertTrue("Job with metered connectivity constraint fired on a metered wifi network.",
+ kTestEnvironment.awaitTimeout());
+ }
+
+ /**
* Schedule a job with a cellular connectivity constraint, and ensure that it executes
* on a mobile data connection and is not stopped when Data Saver is turned on because the app
* is in the foreground.
*/
public void testCellularConstraintExecutedAndStopped_Foreground() throws Exception {
- if (!checkDeviceSupportsMobileData()) {
+ if (mHasWifi) {
+ setMeteredState(true);
+ } else if (checkDeviceSupportsMobileData()) {
+ disconnectWifiToConnectToMobile();
+ } else {
+ // No mobile or wifi.
return;
}
- disconnectWifiToConnectToMobile();
+
mTestAppInterface = new TestAppInterface(mContext, CONNECTIVITY_JOB_ID);
mTestAppInterface.startAndKeepTestActivity();
mTestAppInterface.scheduleJob(false, true, false);
runSatisfiedJob(CONNECTIVITY_JOB_ID);
- assertTrue("Job with metered connectivity constraint did not fire on mobile.",
+ assertTrue("Job with metered connectivity constraint did not fire on a metered network.",
mTestAppInterface.awaitJobStart(30_000));
setDataSaverEnabled(true);
@@ -328,8 +389,12 @@
Log.d(TAG, "App standby not enabled");
return;
}
- if (!checkDeviceSupportsMobileData()) {
- Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+ if (mHasWifi) {
+ setMeteredState(true);
+ } else if (checkDeviceSupportsMobileData()) {
+ disconnectWifiToConnectToMobile();
+ } else {
+ Log.d(TAG, "Skipping test that requires a metered network.");
return;
}
@@ -338,7 +403,6 @@
mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
SystemUtil.runShellCommand("am set-standby-bucket "
+ kJobServiceComponent.getPackageName() + " restricted");
- disconnectWifiToConnectToMobile();
BatteryUtils.runDumpsysBatteryUnplug();
kTestEnvironment.setExpectedExecutions(1);
@@ -361,12 +425,15 @@
Log.d(TAG, "Skipping test that requires battery saver support");
return;
}
- if (!checkDeviceSupportsMobileData()) {
- Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+ if (mHasWifi) {
+ setMeteredState(true);
+ } else if (checkDeviceSupportsMobileData()) {
+ disconnectWifiToConnectToMobile();
+ } else {
+ Log.d(TAG, "Skipping test that requires a metered.");
return;
}
- disconnectWifiToConnectToMobile();
BatteryUtils.runDumpsysBatteryUnplug();
BatteryUtils.enableBatterySaver(true);
@@ -387,11 +454,14 @@
* when Data Saver is on and the device is not connected to WiFi.
*/
public void testExpeditedJobExecutes_DataSaverOn() throws Exception {
- if (!checkDeviceSupportsMobileData()) {
- Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+ if (mHasWifi) {
+ setMeteredState(true);
+ } else if (checkDeviceSupportsMobileData()) {
+ disconnectWifiToConnectToMobile();
+ } else {
+ Log.d(TAG, "Skipping test that requires a metered network.");
return;
}
- disconnectWifiToConnectToMobile();
setDataSaverEnabled(true);
kTestEnvironment.setExpectedExecutions(1);
@@ -414,8 +484,12 @@
Log.d(TAG, "Skipping test that requires battery saver support");
return;
}
- if (!checkDeviceSupportsMobileData()) {
- Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
+ if (mHasWifi) {
+ setMeteredState(true);
+ } else if (checkDeviceSupportsMobileData()) {
+ disconnectWifiToConnectToMobile();
+ } else {
+ Log.d(TAG, "Skipping test that requires a metered network.");
return;
}
if (!AppStandbyUtils.isAppStandbyEnabled()) {
@@ -428,7 +502,6 @@
mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
SystemUtil.runShellCommand("am set-standby-bucket "
+ kJobServiceComponent.getPackageName() + " restricted");
- disconnectWifiToConnectToMobile();
BatteryUtils.runDumpsysBatteryUnplug();
BatteryUtils.enableBatterySaver(true);
setDataSaverEnabled(true);
@@ -494,7 +567,7 @@
runSatisfiedJob(CONNECTIVITY_JOB_ID);
assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
- JobParameters params = kTestEnvironment.getLastJobParameters();
+ JobParameters params = kTestEnvironment.getLastStartJobParameters();
assertNotNull(params.getNetwork());
final NetworkCapabilities capabilities =
getContext().getSystemService(ConnectivityManager.class)
@@ -513,7 +586,7 @@
runSatisfiedJob(CONNECTIVITY_JOB_ID);
assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
- params = kTestEnvironment.getLastJobParameters();
+ params = kTestEnvironment.getLastStartJobParameters();
assertNull(params.getNetwork());
// No network requested
@@ -524,7 +597,7 @@
runSatisfiedJob(CONNECTIVITY_JOB_ID);
assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
- params = kTestEnvironment.getLastJobParameters();
+ params = kTestEnvironment.getLastStartJobParameters();
assertNull(params.getNetwork());
}
@@ -591,7 +664,7 @@
Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
return;
}
- connectToWifi();
+ setMeteredState(false);
kTestEnvironment.setExpectedExecutions(0);
mJobScheduler.schedule(
@@ -604,6 +677,27 @@
}
/**
+ * Schedule a job that requires an unmetered connection, and verify that it does not run when
+ * the device is connected to a metered WiFi provider.
+ */
+ public void testUnmeteredConstraintFails_withMeteredWiFi() throws Exception {
+ if (!mHasWifi) {
+ Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
+ return;
+ }
+ setMeteredState(true);
+
+ kTestEnvironment.setExpectedExecutions(0);
+ mJobScheduler.schedule(
+ mBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+ .build());
+ runSatisfiedJob(CONNECTIVITY_JOB_ID);
+
+ assertTrue("Job requiring unmetered connectivity still executed on metered WiFi.",
+ kTestEnvironment.awaitTimeout());
+ }
+
+ /**
* Schedule a job that requires a cellular connection, and verify that it does not run when
* the device is connected to a WiFi provider.
*/
@@ -616,7 +710,7 @@
Log.d(TAG, "Skipping test that requires the device be mobile data enabled.");
return;
}
- connectToWifi();
+ setMeteredState(false);
kTestEnvironment.setExpectedExecutions(0);
mJobScheduler.schedule(
@@ -653,6 +747,60 @@
return false;
}
+ private 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;
+ }
+ }
+
+ private String getWifiSSID() {
+ final AtomicReference<String> ssid = new AtomicReference<>();
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ ssid.set(mWifiManager.getConnectionInfo().getSSID());
+ }, Manifest.permission.ACCESS_FINE_LOCATION);
+ return unquoteSSID(ssid.get());
+ }
+
+ // Returns "true", "false" or "none"
+ private String getWifiMeteredStatus(String ssid) {
+ // Interestingly giving the SSID as an argument to list wifi-networks
+ // only works iff the network in question has the "false" policy.
+ // Also unfortunately runShellCommand does not pass the command to the interpreter
+ // so it's not possible to | grep the ssid.
+ final String command = "cmd netpolicy list wifi-networks";
+ final String policyString = SystemUtil.runShellCommand(command);
+
+ final Matcher m = Pattern.compile("^" + ssid + ";(true|false|none)$",
+ Pattern.MULTILINE | Pattern.UNIX_LINES).matcher(policyString);
+ if (!m.find()) {
+ fail("Unexpected format from cmd netpolicy (when looking for " + ssid + "): "
+ + policyString);
+ }
+ return m.group(1);
+ }
+
+ private void setMeteredState(boolean metered) {
+ setMeteredState(metered ? "true" : "false");
+ }
+
+ // metered should be "true", "false" or "none"
+ private void setMeteredState(String metered) {
+ final String ssid = getWifiSSID();
+ setMeteredState(ssid, metered);
+ }
+
+ private void setMeteredState(String ssid, String metered) {
+ if (metered.equals(getWifiMeteredStatus(ssid))) {
+ return;
+ }
+ SystemUtil.runShellCommand("cmd netpolicy set metered-network " + ssid + " " + metered);
+ assertEquals(getWifiMeteredStatus(ssid), metered);
+ }
+
/**
* Ensure WiFi is enabled, and block until we've verified that we are in fact connected.
*/
@@ -685,9 +833,11 @@
static void setWifiState(final boolean enable,
final ConnectivityManager cm, final WifiManager wm) throws InterruptedException {
if (enable != isWiFiConnected(cm, wm)) {
- NetworkRequest nr = new NetworkRequest.Builder().addCapability(
- NetworkCapabilities.NET_CAPABILITY_NOT_METERED).build();
- NetworkTracker tracker = new NetworkTracker(false, enable, cm);
+ NetworkRequest nr = new NetworkRequest.Builder().clearCapabilities().build();
+ NetworkCapabilities nc = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build();
+ NetworkTracker tracker = new NetworkTracker(nc, enable, cm);
cm.registerNetworkCallback(nr, tracker);
if (enable) {
@@ -709,8 +859,9 @@
}
}
- private static boolean isWiFiConnected(final ConnectivityManager cm, final WifiManager wm) {
- return wm.isWifiEnabled() && cm.getActiveNetwork() != null && !cm.isActiveNetworkMetered();
+ static boolean isWiFiConnected(final ConnectivityManager cm, final WifiManager wm) {
+ return wm.isWifiEnabled() && cm.getActiveNetwork() != null
+ && cm.getNetworkCapabilities(cm.getActiveNetwork()).hasTransport(TRANSPORT_WIFI);
}
/**
@@ -723,8 +874,11 @@
*/
private void disconnectWifiToConnectToMobile() throws InterruptedException {
if (mHasWifi && mWifiManager.isWifiEnabled()) {
- NetworkRequest nr = new NetworkRequest.Builder().build();
- NetworkTracker tracker = new NetworkTracker(true, true, mCm);
+ NetworkRequest nr = new NetworkRequest.Builder().clearCapabilities().build();
+ NetworkCapabilities nc = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .build();
+ NetworkTracker tracker = new NetworkTracker(nc, true, mCm);
mCm.registerNetworkCallback(nr, tracker);
disconnectFromWifi();
@@ -783,7 +937,7 @@
private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
- private final boolean mNetworkMetered;
+ private final NetworkCapabilities mExpectedCapabilities;
private final boolean mExpectedConnected;
@@ -796,9 +950,9 @@
}
};
- private NetworkTracker(boolean networkMetered, boolean expectedConnected,
+ private NetworkTracker(NetworkCapabilities expectedCapabilities, boolean expectedConnected,
ConnectivityManager cm) {
- mNetworkMetered = networkMetered;
+ mExpectedCapabilities = expectedCapabilities;
mExpectedConnected = expectedConnected;
mCm = cm;
}
@@ -821,20 +975,23 @@
}
private void checkActiveNetwork() {
+ mHandler.removeMessages(MSG_CHECK_ACTIVE_NETWORK);
if (mReceiveLatch.getCount() == 0) {
return;
}
+ Network activeNetwork = mCm.getActiveNetwork();
if (mExpectedConnected) {
- if (mCm.getActiveNetwork() != null
- && mNetworkMetered == mCm.isActiveNetworkMetered()) {
+ if (activeNetwork != null && mExpectedCapabilities.satisfiedByNetworkCapabilities(
+ mCm.getNetworkCapabilities(activeNetwork))) {
mReceiveLatch.countDown();
} else {
mHandler.sendEmptyMessageDelayed(MSG_CHECK_ACTIVE_NETWORK, 5000);
}
} else {
- if (mCm.getActiveNetwork() != null
- && mNetworkMetered != mCm.isActiveNetworkMetered()) {
+ if (activeNetwork == null
+ || !mExpectedCapabilities.satisfiedByNetworkCapabilities(
+ mCm.getNetworkCapabilities(activeNetwork))) {
mReceiveLatch.countDown();
} else {
mHandler.sendEmptyMessageDelayed(MSG_CHECK_ACTIVE_NETWORK, 5000);
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
index bd0653b..31e6cd9 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
@@ -24,6 +24,10 @@
import android.content.Intent;
import android.os.Bundle;
import android.os.PersistableBundle;
+import android.os.UserHandle;
+
+import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.SystemUtil;
/**
* Tests related to JobParameters objects.
@@ -43,7 +47,7 @@
runSatisfiedJob(JOB_ID);
assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
- JobParameters params = kTestEnvironment.getLastJobParameters();
+ JobParameters params = kTestEnvironment.getLastStartJobParameters();
assertEquals(clipData.getItemCount(), params.getClipData().getItemCount());
assertEquals(clipData.getItemAt(0).getText(), params.getClipData().getItemAt(0).getText());
assertEquals(grantFlags, params.getClipGrantFlags());
@@ -61,7 +65,7 @@
runSatisfiedJob(JOB_ID);
assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
- JobParameters params = kTestEnvironment.getLastJobParameters();
+ JobParameters params = kTestEnvironment.getLastStartJobParameters();
assertTrue(persistableBundleEquals(pb, params.getExtras()));
}
@@ -75,7 +79,7 @@
runSatisfiedJob(JOB_ID);
assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
- JobParameters params = kTestEnvironment.getLastJobParameters();
+ JobParameters params = kTestEnvironment.getLastStartJobParameters();
assertTrue(params.isExpeditedJob());
ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
@@ -87,7 +91,7 @@
runSatisfiedJob(JOB_ID);
assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
- params = kTestEnvironment.getLastJobParameters();
+ params = kTestEnvironment.getLastStartJobParameters();
assertFalse(params.isExpeditedJob());
}
@@ -100,12 +104,60 @@
runSatisfiedJob(JOB_ID);
assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
- JobParameters params = kTestEnvironment.getLastJobParameters();
+ JobParameters params = kTestEnvironment.getLastStartJobParameters();
assertEquals(JOB_ID, params.getJobId());
}
// JobParameters.getNetwork() tested in ConnectivityConstraintTest.
+ public void testStopReason() throws Exception {
+ verifyStopReason(new JobInfo.Builder(JOB_ID, kJobServiceComponent).build(),
+ JobParameters.STOP_REASON_TIMEOUT,
+ () -> SystemUtil.runShellCommand(getInstrumentation(),
+ "cmd jobscheduler timeout"
+ + " -u " + UserHandle.myUserId()
+ + " " + kJobServiceComponent.getPackageName()
+ + " " + JOB_ID));
+
+ BatteryUtils.runDumpsysBatterySetLevel(100);
+ BatteryUtils.runDumpsysBatteryUnplug();
+ verifyStopReason(new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setRequiresBatteryNotLow(true).build(),
+ JobParameters.STOP_REASON_CONSTRAINT_BATTERY_NOT_LOW,
+ () -> BatteryUtils.runDumpsysBatterySetLevel(5));
+
+ BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+ BatteryUtils.runDumpsysBatterySetLevel(100);
+ verifyStopReason(new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setRequiresCharging(true).build(),
+ JobParameters.STOP_REASON_CONSTRAINT_CHARGING,
+ BatteryUtils::runDumpsysBatteryUnplug);
+
+ setStorageStateLow(false);
+ verifyStopReason(new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+ .setRequiresStorageNotLow(true).build(),
+ JobParameters.STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW,
+ () -> setStorageStateLow(true));
+ }
+
+ private void verifyStopReason(JobInfo ji, int stopReason, ExceptionRunnable stopCode)
+ throws Exception {
+ kTestEnvironment.setExpectedExecutions(1);
+ kTestEnvironment.setContinueAfterStart();
+ kTestEnvironment.setExpectedStopped();
+ mJobScheduler.schedule(ji);
+ runSatisfiedJob(ji.getId());
+ assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+ JobParameters params = kTestEnvironment.getLastStartJobParameters();
+ assertEquals(JobParameters.STOP_REASON_UNDEFINED, params.getStopReason());
+
+ stopCode.run();
+ assertTrue("Job didn't stop immediately", kTestEnvironment.awaitStopped());
+ params = kTestEnvironment.getLastStopJobParameters();
+ assertEquals(stopReason, params.getStopReason());
+ }
+
public void testTransientExtras() throws Exception {
final Bundle b = new Bundle();
b.putBoolean("random_bool", true);
@@ -118,7 +170,7 @@
runSatisfiedJob(JOB_ID);
assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
- JobParameters params = kTestEnvironment.getLastJobParameters();
+ JobParameters params = kTestEnvironment.getLastStartJobParameters();
assertEquals(b.size(), params.getTransientExtras().size());
for (String key : b.keySet()) {
assertEquals(b.get(key), params.getTransientExtras().get(key));
@@ -128,4 +180,8 @@
// JobParameters.getTriggeredContentAuthorities() tested in TriggerContentTest.
// JobParameters.getTriggeredContentUris() tested in TriggerContentTest.
// JobParameters.isOverrideDeadlineExpired() tested in TimingConstraintTest.
+
+ private interface ExceptionRunnable {
+ void run() throws Exception;
+ }
}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
index 26f6a0a..d9ecad5 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -17,17 +17,22 @@
package android.jobscheduler.cts;
import static android.jobscheduler.cts.ConnectivityConstraintTest.ensureSavedWifiNetwork;
+import static android.jobscheduler.cts.ConnectivityConstraintTest.isWiFiConnected;
import static android.jobscheduler.cts.ConnectivityConstraintTest.setWifiState;
import static android.jobscheduler.cts.TestAppInterface.TEST_APP_PACKAGE;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static android.os.PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED;
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
+import static org.junit.Assert.assertEquals;
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.AppOpsManager;
+import android.app.job.JobParameters;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -53,6 +58,7 @@
import com.android.compatibility.common.util.AppOpsUtils;
import com.android.compatibility.common.util.AppStandbyUtils;
import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.CallbackAsserter;
import com.android.compatibility.common.util.DeviceConfigStateHelper;
import com.android.compatibility.common.util.ThermalUtils;
@@ -189,7 +195,7 @@
public void testAllowWhileIdleJobInTempwhitelist() throws Exception {
assumeTrue("device idle not enabled", mDeviceIdleEnabled);
- toggleDeviceIdleState(true);
+ toggleDozeState(true);
Thread.sleep(DEFAULT_WAIT_TIMEOUT);
sendScheduleJobBroadcast(true);
assertFalse("Job started without being tempwhitelisted",
@@ -207,14 +213,14 @@
runJob();
assertTrue("Job did not start after scheduling",
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- toggleDeviceIdleState(true);
+ toggleDozeState(true);
assertTrue("Job did not stop on entering doze",
mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
+ setScreenState(true);
mTestAppInterface.startAndKeepTestActivity();
- toggleDeviceIdleState(false);
assertTrue("Job for foreground app did not start immediately when device exited doze",
- mTestAppInterface.awaitJobStart(3_000));
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
}
@Test
@@ -225,11 +231,11 @@
runJob();
assertTrue("Job did not start after scheduling",
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- toggleDeviceIdleState(true);
+ toggleDozeState(true);
assertTrue("Job did not stop on entering doze",
mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
- toggleDeviceIdleState(false);
+ toggleDozeState(false);
assertFalse("Job for background app started immediately when device exited doze",
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
Thread.sleep(BACKGROUND_JOBS_EXPECTED_DELAY - DEFAULT_WAIT_TIMEOUT);
@@ -247,6 +253,8 @@
setTestPackageRestricted(true);
assertTrue("Job did not stop after test app was restricted",
mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+ mTestAppInterface.getLastParams().getStopReason());
}
@Test
@@ -271,6 +279,61 @@
mTestAppInterface.startAndKeepTestActivity(true);
assertTrue("Job did not start when app had an activity",
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+ mTestAppInterface.closeActivity();
+ // Don't put full minute as the timeout to give some leeway with test timing/processing.
+ assertFalse("Job stopped within grace period after activity closed",
+ mTestAppInterface.awaitJobStop(55_000L));
+ assertTrue("Job did not stop after grace period ended",
+ mTestAppInterface.awaitJobStop(15_000L));
+ assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+ mTestAppInterface.getLastParams().getStopReason());
+ }
+
+ @Test
+ public void testEJStoppedWhenRestricted() throws Exception {
+ mTestAppInterface.scheduleJob(false, false, true);
+ runJob();
+ assertTrue("Job did not start after scheduling",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ setTestPackageRestricted(true);
+ assertTrue("Job did not stop after test app was restricted",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+ mTestAppInterface.getLastParams().getStopReason());
+ }
+
+ @Test
+ public void testRestrictedEJStartedWhenUnrestricted() throws Exception {
+ setTestPackageRestricted(true);
+ mTestAppInterface.scheduleJob(false, false, true);
+ assertFalse("Job started for restricted app",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ setTestPackageRestricted(false);
+ assertTrue("Job did not start when app was unrestricted",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ }
+
+ @Test
+ public void testRestrictedEJAllowedWhenUidActive() throws Exception {
+ setTestPackageRestricted(true);
+ mTestAppInterface.scheduleJob(false, false, true);
+ assertFalse("Job started for restricted app",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ // Turn the screen on to ensure the app gets into the TOP state.
+ setScreenState(true);
+ mTestAppInterface.startAndKeepTestActivity(true);
+ assertTrue("Job did not start when app had an activity",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+ mTestAppInterface.closeActivity();
+ // Don't put full minute as the timeout to give some leeway with test timing/processing.
+ assertFalse("Job stopped within grace period after activity closed",
+ mTestAppInterface.awaitJobStop(55_000L));
+ assertTrue("Job did not stop after grace period ended",
+ mTestAppInterface.awaitJobStop(15_000L));
+ assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
+ mTestAppInterface.getLastParams().getStopReason());
}
@RequiresDevice // Emulators don't always have access to wifi/network
@@ -497,14 +560,12 @@
assertFalse("New job started with battery saver ON",
mTestAppInterface.awaitJobStart(3_000));
-
// Then make the UID active. Now the job should run.
tempWhitelistTestApp(120_000);
assertTrue("New job in uid-active app failed to start with battery saver OFF",
mTestAppInterface.awaitJobStart(120_000));
}
-
@Test
public void testExpeditedJobBypassesBatterySaverOn() throws Exception {
assumeFalse("not testable in automotive device", mAutomotiveDevice);
@@ -540,7 +601,7 @@
public void testExpeditedJobBypassesDeviceIdle() throws Exception {
assumeTrue("device idle not enabled", mDeviceIdleEnabled);
- toggleDeviceIdleState(true);
+ toggleDozeState(true);
mTestAppInterface.scheduleJob(false, false, true);
runJob();
assertTrue("Job did not start after scheduling",
@@ -551,12 +612,12 @@
public void testExpeditedJobBypassesDeviceIdle_toggling() throws Exception {
assumeTrue("device idle not enabled", mDeviceIdleEnabled);
- toggleDeviceIdleState(false);
+ toggleDozeState(false);
mTestAppInterface.scheduleJob(false, false, true);
runJob();
assertTrue("Job did not start after scheduling",
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- toggleDeviceIdleState(true);
+ toggleDozeState(true);
assertFalse("Job stopped when device enabled turned on",
mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
}
@@ -566,12 +627,14 @@
assumeTrue("device idle not enabled", mDeviceIdleEnabled);
mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(60_000L));
- toggleDeviceIdleState(true);
+ toggleDozeState(true);
mTestAppInterface.scheduleJob(false, false, true);
runJob();
assertTrue("Job did not start after scheduling",
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
assertTrue("Job did not stop after timeout", mTestAppInterface.awaitJobStop(70_000L));
+ assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+ mTestAppInterface.getLastParams().getStopReason());
// Should be rescheduled.
assertJobNotReady();
assertJobWaiting();
@@ -581,7 +644,7 @@
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
// Should start when Doze is turned off.
- toggleDeviceIdleState(false);
+ toggleDozeState(false);
assertTrue("Job did not start after Doze turned off",
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
}
@@ -599,6 +662,8 @@
assertTrue("Job did not start after scheduling",
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
assertTrue("Job did not stop after timeout", mTestAppInterface.awaitJobStop(70_000L));
+ assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+ mTestAppInterface.getLastParams().getStopReason());
// Should be rescheduled.
assertJobNotReady();
assertJobWaiting();
@@ -620,18 +685,20 @@
mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(60_000L));
BatteryUtils.runDumpsysBatteryUnplug();
- toggleDeviceIdleState(true);
+ toggleDozeState(true);
mTestAppInterface.scheduleJob(false, false, true);
runJob();
assertTrue("Job did not start after scheduling",
mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
assertTrue("Job did not stop after timeout", mTestAppInterface.awaitJobStop(70_000L));
+ assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+ mTestAppInterface.getLastParams().getStopReason());
// Should be rescheduled.
assertJobNotReady();
assertJobWaiting();
// Battery saver kicks in before Doze ends. Job shouldn't start while BS is on.
BatteryUtils.enableBatterySaver(true);
- toggleDeviceIdleState(false);
+ toggleDozeState(false);
Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
runJob();
assertFalse("Job started while power restrictions active after timing out",
@@ -648,7 +715,7 @@
assumeTrue("device idle not enabled", mDeviceIdleEnabled);
mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(60_000L));
- toggleDeviceIdleState(false);
+ toggleDozeState(false);
mTestAppInterface.scheduleJob(false, false, true);
runJob();
assertTrue("Job did not start after scheduling",
@@ -657,9 +724,11 @@
assertFalse("Job stopped after min runtime", mTestAppInterface.awaitJobStop(90_000L));
// Should stop when Doze is turned on.
- toggleDeviceIdleState(true);
+ toggleDozeState(true);
assertTrue("Job did not stop after Doze turned on",
mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+ mTestAppInterface.getLastParams().getStopReason());
}
@Test
@@ -681,6 +750,142 @@
BatteryUtils.enableBatterySaver(true);
assertTrue("Job did not stop after battery saver turned on",
mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+ mTestAppInterface.getLastParams().getStopReason());
+ }
+
+ @Test
+ public void testRestrictingStopReason_RestrictedBucket() throws Exception {
+ assumeTrue("app standby not enabled", mAppStandbyEnabled);
+ assumeFalse("not testable in automotive device", mAutomotiveDevice);
+ assumeFalse("not testable in leanback device", mLeanbackOnly);
+
+ assumeTrue(mHasWifi);
+ ensureSavedWifiNetwork(mWifiManager);
+
+ setRestrictedBucketEnabled(true);
+ setTestPackageStandbyBucket(Bucket.RESTRICTED);
+
+ // Disable coalescing and the parole session
+ mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
+ mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
+
+ // Satisfy all additional constraints.
+ setAirplaneMode(false);
+ setWifiState(true, mCm, mWifiManager);
+ BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+ BatteryUtils.runDumpsysBatterySetLevel(100);
+ setScreenState(false);
+ triggerJobIdle();
+
+ // Toggle individual constraints
+
+ // Connectivity
+ mTestAppInterface.scheduleJob(false, true, false);
+ runJob();
+ assertTrue("New job didn't start in RESTRICTED bucket",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ setAirplaneMode(true);
+ assertTrue("New job didn't stop when connectivity dropped",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ assertEquals(JobParameters.STOP_REASON_CONSTRAINT_CONNECTIVITY,
+ mTestAppInterface.getLastParams().getStopReason());
+ setAirplaneMode(false);
+ setWifiState(true, mCm, mWifiManager);
+
+ // Idle
+ mTestAppInterface.scheduleJob(false, true, false);
+ runJob();
+ assertTrue("New job didn't start in RESTRICTED bucket",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ setScreenState(true);
+ assertTrue("New job didn't stop when device no longer idle",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
+ mTestAppInterface.getLastParams().getStopReason());
+ setScreenState(false);
+ triggerJobIdle();
+
+ // Charging
+ mTestAppInterface.scheduleJob(false, true, false);
+ runJob();
+ assertTrue("New job didn't start in RESTRICTED bucket",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ BatteryUtils.runDumpsysBatteryUnplug();
+ assertTrue("New job didn't stop when device no longer charging",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
+ mTestAppInterface.getLastParams().getStopReason());
+ BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+ BatteryUtils.runDumpsysBatterySetLevel(100);
+
+ // Battery not low
+ setScreenState(false);
+ triggerJobIdle();
+ mTestAppInterface.scheduleJob(false, true, false);
+ runJob();
+ assertTrue("New job didn't start in RESTRICTED bucket",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+ BatteryUtils.runDumpsysBatterySetLevel(1);
+ assertTrue("New job didn't stop when battery too low",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
+ mTestAppInterface.getLastParams().getStopReason());
+ }
+
+ @Test
+ public void testRestrictingStopReason_Quota() throws Exception {
+ // Reduce allowed time for testing.
+ mDeviceConfigStateHelper.set("qc_allowed_time_per_period_ms", "60000");
+ BatteryUtils.runDumpsysBatteryUnplug();
+ setTestPackageStandbyBucket(Bucket.RARE);
+
+ sendScheduleJobBroadcast(false);
+ runJob();
+ assertTrue("New job didn't start",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+ Thread.sleep(60000);
+
+ assertTrue("New job didn't stop after using up quota",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ assertEquals(JobParameters.STOP_REASON_QUOTA,
+ mTestAppInterface.getLastParams().getStopReason());
+ }
+
+ @Test
+ public void testRestrictingStopReason_BatterySaver() throws Exception {
+ BatteryUtils.assumeBatterySaverFeature();
+
+ BatteryUtils.runDumpsysBatteryUnplug();
+ BatteryUtils.enableBatterySaver(false);
+ sendScheduleJobBroadcast(false);
+ runJob();
+ assertTrue("Job did not start after scheduling",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+ BatteryUtils.enableBatterySaver(true);
+ assertTrue("Job did not stop on entering battery saver",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+ mTestAppInterface.getLastParams().getStopReason());
+ }
+
+ @Test
+ public void testRestrictingStopReason_Doze() throws Exception {
+ assumeTrue("device idle not enabled", mDeviceIdleEnabled);
+
+ toggleDozeState(false);
+ sendScheduleJobBroadcast(false);
+ runJob();
+ assertTrue("Job did not start after scheduling",
+ mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+ toggleDozeState(true);
+ assertTrue("Job did not stop on entering doze",
+ mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+ assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
+ mTestAppInterface.getLastParams().getStopReason());
}
@After
@@ -689,7 +894,7 @@
// Lock thermal service to not throttling
ThermalUtils.overrideThermalNotThrottling();
if (mDeviceIdleEnabled) {
- toggleDeviceIdleState(false);
+ toggleDozeState(false);
}
mTestAppInterface.cleanup();
BatteryUtils.runDumpsysBatteryReset();
@@ -697,7 +902,7 @@
removeTestAppFromTempWhitelist();
// Ensure that we leave WiFi in its previous state.
- if (mWifiManager.isWifiEnabled() != mInitialWiFiState) {
+ if (mHasWifi && mWifiManager.isWifiEnabled() != mInitialWiFiState) {
try {
setWifiState(mInitialWiFiState, mCm, mWifiManager);
} catch (AssertionFailedError e) {
@@ -743,7 +948,7 @@
mTestAppInterface.scheduleJob(allowWhileIdle, false, false);
}
- private void toggleDeviceIdleState(final boolean idle) throws Exception {
+ private void toggleDozeState(final boolean idle) throws Exception {
mUiDevice.executeShellCommand("cmd deviceidle " + (idle ? "force-idle" : "unforce"));
assertTrue("Could not change device idle state to " + idle,
waitUntilTrue(SHELL_TIMEOUT, () -> {
@@ -836,12 +1041,27 @@
return "enabled".equals(output);
}
- private void setAirplaneMode(boolean on) throws IOException {
- if (on) {
- mUiDevice.executeShellCommand("cmd connectivity airplane-mode enable");
- } else {
- mUiDevice.executeShellCommand("cmd connectivity airplane-mode disable");
- }
+ private void setAirplaneMode(boolean on) throws Exception {
+ final CallbackAsserter airplaneModeBroadcastAsserter = CallbackAsserter.forBroadcast(
+ new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+ mUiDevice.executeShellCommand(
+ "cmd connectivity airplane-mode " + (on ? "enable" : "disable"));
+ airplaneModeBroadcastAsserter.assertCalled("Didn't get airplane mode changed broadcast",
+ 15 /* 15 seconds */);
+ waitUntil("Networks didn't change to " + (!on ? " on" : " off"), 60_000,
+ () -> {
+ if (on) {
+ return mCm.getActiveNetwork() == null
+ && (!mHasWifi || !isWiFiConnected(mCm, mWifiManager));
+ } else {
+ return mCm.getActiveNetwork() != null;
+ }
+ });
+ // Wait some time for the network changes to propagate. Can't use
+ // waitUntil(isAirplaneModeOn() == on) because the response quickly gives the new
+ // airplane mode status even though the network changes haven't propagated all the way to
+ // JobScheduler.
+ Thread.sleep(5000);
}
private String getJobState() throws Exception {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java b/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
index 4c48b9d..8f11408 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/TestAppInterface.java
@@ -75,7 +75,7 @@
cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER));
cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.sendBroadcast(cancelJobsIntent);
- mContext.sendBroadcast(new Intent(TestActivity.ACTION_FINISH_ACTIVITY));
+ closeActivity();
mContext.unregisterReceiver(mReceiver);
mTestJobState.reset();
}
@@ -112,6 +112,10 @@
}
}
+ void closeActivity() {
+ mContext.sendBroadcast(new Intent(TestActivity.ACTION_FINISH_ACTIVITY));
+ }
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -124,6 +128,7 @@
synchronized (mTestJobState) {
mTestJobState.running = ACTION_JOB_STARTED.equals(intent.getAction());
mTestJobState.jobId = params.getJobId();
+ mTestJobState.params = params;
if (intent.getBooleanExtra(EXTRA_REQUEST_JOB_UID_STATE, false)) {
mTestJobState.procState = intent.getIntExtra(JOB_PROC_STATE_KEY,
ActivityManager.PROCESS_STATE_NONEXISTENT);
@@ -174,12 +179,19 @@
return condition.isTrue();
}
+ JobParameters getLastParams() {
+ synchronized (mTestJobState) {
+ return mTestJobState.params;
+ }
+ }
+
private static final class TestJobState {
int jobId;
boolean running;
int procState;
int capabilities;
int oomScoreAdj;
+ JobParameters params;
TestJobState() {
initState();
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
index b45808d..03f1468 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
@@ -118,7 +118,7 @@
mJobScheduler.schedule(deadlineJob);
assertTrue("Failed to execute deadline job", kTestEnvironment.awaitExecution());
assertTrue("Job does not show its deadline as expired",
- kTestEnvironment.getLastJobParameters().isOverrideDeadlineExpired());
+ kTestEnvironment.getLastStartJobParameters().isOverrideDeadlineExpired());
}
@@ -140,6 +140,6 @@
assertTrue("Failed to execute non-deadline job", kTestEnvironment.awaitExecution());
assertFalse("Job that ran early (unexpired) didn't have" +
" JobParameters#isOverrideDeadlineExpired=false",
- kTestEnvironment.getLastJobParameters().isOverrideDeadlineExpired());
+ kTestEnvironment.getLastStartJobParameters().isOverrideDeadlineExpired());
}
}
diff --git a/tests/MediaProviderTranscode/Android.bp b/tests/MediaProviderTranscode/Android.bp
new file mode 100644
index 0000000..4ba3243
--- /dev/null
+++ b/tests/MediaProviderTranscode/Android.bp
@@ -0,0 +1,61 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsMediaProviderTranscodeTests",
+ test_suites: [
+ "device-tests",
+ "cts",
+ ],
+ compile_multilib: "both",
+
+ manifest: "AndroidManifest.xml",
+
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ libs: [
+ "android.test.base",
+ "android.test.mock",
+ "android.test.runner",
+ ],
+
+ static_libs: [
+ "androidx.test.rules",
+ "cts-install-lib",
+ "collector-device-lib-platform",
+ "mockito-target",
+ "truth-prebuilt",
+ ],
+
+ certificate: "media",
+ java_resources: [":CtsTranscodeTestAppSupportsHevc", ":CtsTranscodeTestAppSupportsSlowMotion"]
+}
+
+android_test_helper_app {
+ name: "CtsTranscodeTestAppSupportsHevc",
+ manifest: "helper/AndroidManifest.xml",
+ sdk_version: "test_current",
+ resource_dirs: ["helper/res-hevc"],
+ srcs: [
+ "helper/src/**/*.java",
+ "src/android/mediaprovidertranscode/cts/TranscodeTestConstants.java"
+ ],
+ static_libs: ["androidx.legacy_legacy-support-v4"],
+ target_sdk_version: "28",
+}
+
+android_test_helper_app {
+ name: "CtsTranscodeTestAppSupportsSlowMotion",
+ manifest: "helper/AndroidManifest.xml",
+ sdk_version: "test_current",
+ resource_dirs: ["helper/res-slow-motion"],
+ srcs: [
+ "helper/src/**/*.java",
+ "src/android/mediaprovidertranscode/cts/TranscodeTestConstants.java"
+ ],
+ static_libs: ["androidx.legacy_legacy-support-v4"],
+ target_sdk_version: "28",
+}
diff --git a/tests/MediaProviderTranscode/AndroidManifest.xml b/tests/MediaProviderTranscode/AndroidManifest.xml
new file mode 100644
index 0000000..50abad9
--- /dev/null
+++ b/tests/MediaProviderTranscode/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.mediaprovidertranscode.cts">
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
+ <application android:label="MediaProvider Transcode Tests">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.mediaprovidertranscode.cts"
+ android:label="MediaProvider Transcode Tests" />
+
+</manifest>
diff --git a/tests/MediaProviderTranscode/AndroidTest.xml b/tests/MediaProviderTranscode/AndroidTest.xml
new file mode 100644
index 0000000..b1697bd
--- /dev/null
+++ b/tests/MediaProviderTranscode/AndroidTest.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.
+-->
+<configuration description="Runs CTS Tests for MediaProvder.">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="CtsMediaProviderTranscodeTests.apk" />
+ <option name="install-arg" value="-g" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="cts" />
+ <option name="test-tag" value="MediaProviderTranscodeTests" />
+ <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" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.mediaprovidertranscode.cts" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/tests/MediaProviderTranscode/OWNERS b/tests/MediaProviderTranscode/OWNERS
new file mode 100644
index 0000000..5ee8e8c
--- /dev/null
+++ b/tests/MediaProviderTranscode/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 95221
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
\ No newline at end of file
diff --git a/tests/MediaProviderTranscode/helper/AndroidManifest.xml b/tests/MediaProviderTranscode/helper/AndroidManifest.xml
new file mode 100644
index 0000000..a9ad768
--- /dev/null
+++ b/tests/MediaProviderTranscode/helper/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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.mediaprovidertranscode.cts.testapp"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application android:label="Transcode Test App">
+ <property android:name="android.media.PROPERTY_MEDIA_CAPABILITIES"
+ android:resource="@xml/media_capabilities" />
+
+ <activity android:name="android.mediaprovidertranscode.cts.testapp.TranscodeTestHelper"
+ android:exported="true" >
+ <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>
+
+ <provider
+ android:name="androidx.core.content.FileProvider"
+ android:authorities="android.mediaprovidertranscode.cts.testapp"
+ android:exported="false"
+ android:grantUriPermissions="true">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_paths" />
+ </provider>
+ </application>
+</manifest>
diff --git a/tests/MediaProviderTranscode/helper/res-hevc/xml/file_paths.xml b/tests/MediaProviderTranscode/helper/res-hevc/xml/file_paths.xml
new file mode 100644
index 0000000..2d5ccaf
--- /dev/null
+++ b/tests/MediaProviderTranscode/helper/res-hevc/xml/file_paths.xml
@@ -0,0 +1,3 @@
+<external-paths xmlns:android="http://schemas.android.com/apk/res/android">
+ <external-path name="external_files" path="."/>
+</external-paths>
diff --git a/tests/MediaProviderTranscode/helper/res-hevc/xml/media_capabilities.xml b/tests/MediaProviderTranscode/helper/res-hevc/xml/media_capabilities.xml
new file mode 100644
index 0000000..3bff61e
--- /dev/null
+++ b/tests/MediaProviderTranscode/helper/res-hevc/xml/media_capabilities.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+ <format android:name="HEVC" supported="true"/>
+ <format android:name="HDR10" supported="false"/>
+ <format android:name="SlowMotion" supported="false"/>
+</media-capabilities>
diff --git a/tests/MediaProviderTranscode/helper/res-slow-motion/xml/file_paths.xml b/tests/MediaProviderTranscode/helper/res-slow-motion/xml/file_paths.xml
new file mode 100644
index 0000000..2d5ccaf
--- /dev/null
+++ b/tests/MediaProviderTranscode/helper/res-slow-motion/xml/file_paths.xml
@@ -0,0 +1,3 @@
+<external-paths xmlns:android="http://schemas.android.com/apk/res/android">
+ <external-path name="external_files" path="."/>
+</external-paths>
diff --git a/tests/MediaProviderTranscode/helper/res-slow-motion/xml/media_capabilities.xml b/tests/MediaProviderTranscode/helper/res-slow-motion/xml/media_capabilities.xml
new file mode 100644
index 0000000..e96557e
--- /dev/null
+++ b/tests/MediaProviderTranscode/helper/res-slow-motion/xml/media_capabilities.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+ <format android:name="HEVC" supported="false"/>
+ <format android:name="HDR10" supported="false"/>
+ <format android:name="SlowMotion" supported="true"/>
+</media-capabilities>
diff --git a/tests/MediaProviderTranscode/helper/src/android/mediaprovidertranscode/cts/TranscodeTestHelper.java b/tests/MediaProviderTranscode/helper/src/android/mediaprovidertranscode/cts/TranscodeTestHelper.java
new file mode 100644
index 0000000..65857f7
--- /dev/null
+++ b/tests/MediaProviderTranscode/helper/src/android/mediaprovidertranscode/cts/TranscodeTestHelper.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediaprovidertranscode.cts.testapp;
+
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.INTENT_EXTRA_CALLING_PKG;
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.INTENT_EXTRA_PATH;
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.OPEN_FILE_QUERY;
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.INTENT_QUERY_TYPE;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.core.content.FileProvider;
+
+import java.io.File;
+
+/**
+ * Helper app for TranscodeTest.
+ *
+ * <p>Used to perform TranscodeTest functions as a different app. Based on the Query type
+ * app can perform different functions and send the result back to host app.
+ */
+public class TranscodeTestHelper extends Activity {
+ private static final String TAG = "TranscodeTestHelper";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ String queryType = getIntent().getStringExtra(INTENT_QUERY_TYPE);
+ if (!OPEN_FILE_QUERY.equals(queryType)) {
+ throw new IllegalStateException(
+ "Unknown query received from launcher app: " + queryType);
+ }
+
+ final File file = new File(getIntent().getStringExtra(INTENT_EXTRA_PATH));
+ Uri contentUri = FileProvider.getUriForFile(this, getPackageName(), file);
+
+ final Intent intent = new Intent(queryType);
+ intent.putExtra(queryType, contentUri);
+
+ // Grant permission to the calling package
+ getApplicationContext().grantUriPermission(getIntent().getStringExtra(
+ INTENT_EXTRA_CALLING_PKG),
+ contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ sendBroadcast(intent);
+ }
+}
diff --git a/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_long.mp4 b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_long.mp4
new file mode 100755
index 0000000..6b37153
--- /dev/null
+++ b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_long.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_medium.mp4 b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_medium.mp4
new file mode 100755
index 0000000..207530c
--- /dev/null
+++ b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_medium.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_small.mp4 b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_small.mp4
new file mode 100755
index 0000000..f2aa045
--- /dev/null
+++ b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_small.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/res/raw/testVideo_Legacy.mp4 b/tests/MediaProviderTranscode/res/raw/testVideo_Legacy.mp4
new file mode 100755
index 0000000..1c74ffa
--- /dev/null
+++ b/tests/MediaProviderTranscode/res/raw/testVideo_Legacy.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/res/raw/testvideo_HEVC.mp4 b/tests/MediaProviderTranscode/res/raw/testvideo_HEVC.mp4
new file mode 100644
index 0000000..8a3dba2
--- /dev/null
+++ b/tests/MediaProviderTranscode/res/raw/testvideo_HEVC.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java
new file mode 100644
index 0000000..d91e090
--- /dev/null
+++ b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTest.java
@@ -0,0 +1,1042 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediaprovidertranscode.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static android.mediaprovidertranscode.cts.TranscodeTestUtils.assertFileContent;
+import static android.mediaprovidertranscode.cts.TranscodeTestUtils.assertTranscode;
+import static android.mediaprovidertranscode.cts.TranscodeTestUtils.installAppWithStoragePermissions;
+import static android.mediaprovidertranscode.cts.TranscodeTestUtils.open;
+import static android.mediaprovidertranscode.cts.TranscodeTestUtils.openFileAs;
+import static android.mediaprovidertranscode.cts.TranscodeTestUtils.uninstallApp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.Manifest;
+import android.media.ApplicationMediaCapabilities;
+import android.media.MediaFormat;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.MediaStore;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.install.lib.TestApp;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class TranscodeTest {
+ private static final String TAG = "TranscodeTest";
+ private static final File EXTERNAL_STORAGE_DIRECTORY
+ = Environment.getExternalStorageDirectory();
+ private static final File DIR_CAMERA
+ = new File(EXTERNAL_STORAGE_DIRECTORY, Environment.DIRECTORY_DCIM + "/Camera");
+ // TODO(b/169546642): Test other directories like /sdcard and /sdcard/foo
+ // These are the only transcode unsupported directories we can stage files in given our
+ // test app permissions
+ private static final File[] DIRS_NO_TRANSCODE = {
+ new File(EXTERNAL_STORAGE_DIRECTORY, Environment.DIRECTORY_PICTURES),
+ new File(EXTERNAL_STORAGE_DIRECTORY, Environment.DIRECTORY_MOVIES),
+ new File(EXTERNAL_STORAGE_DIRECTORY, Environment.DIRECTORY_DOWNLOADS),
+ new File(EXTERNAL_STORAGE_DIRECTORY, Environment.DIRECTORY_DCIM),
+ new File(EXTERNAL_STORAGE_DIRECTORY, Environment.DIRECTORY_DOCUMENTS),
+ };
+
+ static final String NONCE = String.valueOf(System.nanoTime());
+ private static final String HEVC_FILE_NAME = "TranscodeTestHEVC_" + NONCE + ".mp4";
+ private static final String SMALL_HEVC_FILE_NAME = "TranscodeTestHevcSmall_" + NONCE + ".mp4";
+ private static final String LEGACY_FILE_NAME = "TranscodeTestLegacy_" + NONCE + ".mp4";
+
+ private static final TestApp TEST_APP_HEVC = new TestApp("TestAppHevc",
+ "android.mediaprovidertranscode.cts.testapp", 1, false,
+ "CtsTranscodeTestAppSupportsHevc.apk");
+
+ private static final TestApp TEST_APP_SLOW_MOTION = new TestApp("TestAppSlowMotion",
+ "android.mediaprovidertranscode.cts.testapp", 1, false,
+ "CtsTranscodeTestAppSupportsSlowMotion.apk");
+
+ @Before
+ public void setUp() throws Exception {
+ TranscodeTestUtils.pollForExternalStorageState();
+ TranscodeTestUtils.grantPermission(getContext().getPackageName(),
+ Manifest.permission.READ_EXTERNAL_STORAGE);
+ TranscodeTestUtils.pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, true);
+ TranscodeTestUtils.enableSeamlessTranscoding();
+ TranscodeTestUtils.disableTranscodingForAllPackages();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ TranscodeTestUtils.disableSeamlessTranscoding();
+ }
+
+ /**
+ * Tests that we return FD of transcoded file for legacy apps
+ * @throws Exception
+ */
+ @Test
+ public void testTranscoded_FilePath() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ ParcelFileDescriptor pfdOriginal = open(modernFile, false);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+ ParcelFileDescriptor pfdTranscoded = open(modernFile, false);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal, pfdTranscoded, false);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ /**
+ * Tests that we don't transcode files outside DCIM/Camera
+ * @throws Exception
+ */
+ @Test
+ public void testNoTranscodeOutsideCamera_FilePath() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ List<File> noTranscodeFiles = new ArrayList<>();
+ for (File file : DIRS_NO_TRANSCODE) {
+ noTranscodeFiles.add(new File(file, HEVC_FILE_NAME));
+ }
+ noTranscodeFiles.add(new File(getContext().getExternalFilesDir(null), HEVC_FILE_NAME));
+
+ try {
+ TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+ for (File file : noTranscodeFiles) {
+ TranscodeTestUtils.stageHEVCVideoFile(file);
+ }
+ ParcelFileDescriptor pfdOriginal1 = open(modernFile, false);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ for (File file : noTranscodeFiles) {
+ pfdOriginal1.seekTo(0);
+ ParcelFileDescriptor pfdOriginal2 = open(file, false);
+ assertFileContent(modernFile, file, pfdOriginal1, pfdOriginal2, true);
+ }
+ } finally {
+ modernFile.delete();
+ for (File file : noTranscodeFiles) {
+ file.delete();
+ }
+ }
+ }
+
+ /**
+ * Tests that same transcoded file is used for multiple open() from same app
+ * @throws Exception
+ */
+ @Test
+ public void testSameTranscoded_FilePath() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+ ParcelFileDescriptor pfdTranscoded1 = open(modernFile, false);
+ ParcelFileDescriptor pfdTranscoded2 = open(modernFile, false);
+
+ assertFileContent(modernFile, modernFile, pfdTranscoded1, pfdTranscoded2, true);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ /**
+ * Tests that we return FD of transcoded file for legacy apps
+ * @throws Exception
+ */
+ @Test
+ public void testTranscoded_ContentResolver() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ ParcelFileDescriptor pfdOriginal = open(uri, false, null /* bundle */);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ ParcelFileDescriptor pfdTranscoded = open(uri, false, null /* bundle */);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal, pfdTranscoded, false);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ /**
+ * Tests that we don't transcode files outside DCIM/Camera
+ * @throws Exception
+ */
+ @Test
+ public void testNoTranscodeOutsideCamera_ConentResolver() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ List<File> noTranscodeFiles = new ArrayList<>();
+ for (File file : DIRS_NO_TRANSCODE) {
+ noTranscodeFiles.add(new File(file, HEVC_FILE_NAME));
+ }
+
+ try {
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+ ArrayList<Uri> noTranscodeUris = new ArrayList<>();
+ for (File file : noTranscodeFiles) {
+ noTranscodeUris.add(TranscodeTestUtils.stageHEVCVideoFile(file));
+ }
+
+ ParcelFileDescriptor pfdOriginal1 = open(uri, false, null /* bundle */);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ for (int i = 0; i < noTranscodeUris.size(); i++) {
+ pfdOriginal1.seekTo(0);
+ ParcelFileDescriptor pfdOriginal2 =
+ open(noTranscodeUris.get(i), false, null /* bundle */);
+ assertFileContent(modernFile, noTranscodeFiles.get(1), pfdOriginal1, pfdOriginal2,
+ true);
+ }
+ } finally {
+ modernFile.delete();
+ for (File file : noTranscodeFiles) {
+ file.delete();
+ }
+ }
+ }
+
+ /**
+ * Tests that same transcoded file is used for multiple open() from same app
+ * @throws Exception
+ */
+ @Test
+ public void testSameTranscodedFile_ContentResolver() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ ParcelFileDescriptor pfdTranscoded1 = open(uri, false, null /* bundle */);
+ ParcelFileDescriptor pfdTranscoded2 = open(uri, false, null /* bundle */);
+
+ assertFileContent(modernFile, modernFile, pfdTranscoded1, pfdTranscoded2, true);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ /**
+ * Tests that deletes are visible across legacy and modern apps
+ * @throws Exception
+ */
+ @Test
+ public void testDeleteTranscodedFile_FilePath() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ assertTrue(modernFile.delete());
+ assertFalse(modernFile.exists());
+
+ TranscodeTestUtils.disableTranscodingForAllPackages();
+
+ assertFalse(modernFile.exists());
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ /**
+ * Tests that renames are visible across legacy and modern apps
+ * @throws Exception
+ */
+ @Test
+ public void testRenameTranscodedFile_FilePath() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ File destFile = new File(DIR_CAMERA, "renamed_" + HEVC_FILE_NAME);
+ try {
+ TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ assertTrue(modernFile.renameTo(destFile));
+ assertTrue(destFile.exists());
+ assertFalse(modernFile.exists());
+
+ TranscodeTestUtils.disableTranscodingForAllPackages();
+
+ assertTrue(destFile.exists());
+ assertFalse(modernFile.exists());
+ } finally {
+ modernFile.delete();
+ destFile.delete();
+ }
+ }
+
+ /**
+ * Tests that transcode doesn't start until read(2)
+ * @throws Exception
+ */
+ @Test
+ public void testLazyTranscodedFile_FilePath() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ assertTranscode(modernFile, false);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ assertTranscode(modernFile, true);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ /**
+ * Tests that transcode cache is reused after file path transcode
+ * @throws Exception
+ */
+ @Test
+ public void testTranscodedCacheReuse_FilePath() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ assertTranscode(modernFile, true);
+ assertTranscode(modernFile, false);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ /**
+ * Tests that transcode cache is reused after ContentResolver transcode
+ * @throws Exception
+ */
+ @Test
+ public void testTranscodedCacheReuse_ContentResolver() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ assertTranscode(uri, true);
+ assertTranscode(uri, false);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ /**
+ * Tests that transcode cache is reused after ContentResolver transcode
+ * and file path opens
+ * @throws Exception
+ */
+ @Test
+ public void testTranscodedCacheReuse_ContentResolverFilePath() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ assertTranscode(uri, true);
+ assertTranscode(modernFile, false);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ /**
+ * Tests that transcode cache is reused after file path transcode
+ * and ContentResolver opens
+ * @throws Exception
+ */
+ @Test
+ public void testTranscodedCacheReuse_FilePathContentResolver() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ assertTranscode(modernFile, true);
+ assertTranscode(uri, false);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ /**
+ * Tests that transcode cache is reused after rename
+ * @throws Exception
+ */
+ @Test
+ public void testTranscodedCacheReuseAfterRename_FilePath() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ File destFile = new File(DIR_CAMERA, "renamed_" + HEVC_FILE_NAME);
+ try {
+ TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ assertTranscode(modernFile, true);
+
+ assertTrue(modernFile.renameTo(destFile));
+
+ assertTranscode(destFile, false);
+ } finally {
+ modernFile.delete();
+ destFile.delete();
+ }
+ }
+
+ @Test
+ public void testExtraAcceptOriginalFormatTrue_ContentResolver() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ ParcelFileDescriptor pfdOriginal1 = open(uri, false, null /* bundle */);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(MediaStore.EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT, true);
+ ParcelFileDescriptor pfdOriginal2 = open(uri, false, bundle);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, true);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ @Test
+ public void testExtraAcceptOriginalFormatFalse_ContentResolver() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ ParcelFileDescriptor pfdOriginal = open(uri, false, null /* bundle */);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(MediaStore.EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT, false);
+ ParcelFileDescriptor pfdTranscoded = open(uri, false, bundle);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal, pfdTranscoded, false);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ @Test
+ public void testExtraMediaCapabilitiesHevcSupportedTrue_ContentResolver() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ ParcelFileDescriptor pfdOriginal1 = open(uri, false, null /* bundle */);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ Bundle bundle = new Bundle();
+ ApplicationMediaCapabilities capabilities =
+ new ApplicationMediaCapabilities.Builder()
+ .addSupportedVideoMimeType(MediaFormat.MIMETYPE_VIDEO_HEVC).build();
+ bundle.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES, capabilities);
+ ParcelFileDescriptor pfdOriginal2 = open(uri, false, bundle);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, true);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ @Test
+ public void testExtraMediaCapabilitiesHevcUnsupportedFalse_ContentResolver() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ ParcelFileDescriptor pfdOriginal1 = open(uri, false, null /* bundle */);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ Bundle bundle = new Bundle();
+ ApplicationMediaCapabilities capabilities =
+ new ApplicationMediaCapabilities.Builder()
+ .addUnsupportedVideoMimeType(MediaFormat.MIMETYPE_VIDEO_HEVC).build();
+ bundle.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES, capabilities);
+ ParcelFileDescriptor pfdOriginal2 = open(uri, false, bundle);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, false);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ @Test
+ public void testExtraMediaCapabilitiesHevcUnspecifiedFalse_ContentResolver() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ ParcelFileDescriptor pfdOriginal1 = open(uri, false, null /* bundle */);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ Bundle bundle = new Bundle();
+ ApplicationMediaCapabilities capabilities =
+ new ApplicationMediaCapabilities.Builder().build();
+ bundle.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES, capabilities);
+ ParcelFileDescriptor pfdTranscoded = open(uri, false, bundle);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal1, pfdTranscoded, false);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ @Test
+ public void testExtraAcceptOriginalTrueAndMediaCapabilitiesHevcFalse_ContentResolver()
+ throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ ParcelFileDescriptor pfdOriginal1 = open(uri, false, null /* bundle */);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ Bundle bundle = new Bundle();
+ ApplicationMediaCapabilities capabilities =
+ new ApplicationMediaCapabilities.Builder().build();
+ bundle.putParcelable(MediaStore.EXTRA_MEDIA_CAPABILITIES, capabilities);
+ bundle.putBoolean(MediaStore.EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT, true);
+ ParcelFileDescriptor pfdOriginal2 = open(uri, false, bundle);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, true);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ @Test
+ public void testMediaCapabilitiesManifestHevc()
+ throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ ParcelFileDescriptor pfdOriginal2 = null;
+ try {
+ installAppWithStoragePermissions(TEST_APP_HEVC);
+
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ ParcelFileDescriptor pfdOriginal1 = open(modernFile, false);
+
+ TranscodeTestUtils.enableTranscodingForPackage(TEST_APP_HEVC.getPackageName());
+
+ pfdOriginal2 = openFileAs(TEST_APP_HEVC, modernFile);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, true);
+ } finally {
+ // Explicitly close PFD otherwise instrumention might crash when test_app is uninstalled
+ if (pfdOriginal2 != null) {
+ pfdOriginal2.close();
+ }
+ modernFile.delete();
+ uninstallApp(TEST_APP_HEVC);
+ }
+ }
+
+ @Test
+ public void testMediaCapabilitiesManifestSlowMotion()
+ throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ ParcelFileDescriptor pfdOriginal2 = null;
+ try {
+ installAppWithStoragePermissions(TEST_APP_SLOW_MOTION);
+
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ ParcelFileDescriptor pfdOriginal1 = open(modernFile, false);
+
+ TranscodeTestUtils.enableTranscodingForPackage(TEST_APP_SLOW_MOTION.getPackageName());
+
+ pfdOriginal2 = openFileAs(TEST_APP_SLOW_MOTION, modernFile);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, false);
+ } finally {
+ // Explicitly close PFD otherwise instrumention might crash when test_app is uninstalled
+ if (pfdOriginal2 != null) {
+ pfdOriginal2.close();
+ }
+ modernFile.delete();
+ uninstallApp(TEST_APP_HEVC);
+ }
+ }
+
+ @Test
+ public void testAppCompatNoTranscodeHevc() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ String packageName = TEST_APP_SLOW_MOTION.getPackageName();
+ ParcelFileDescriptor pfdOriginal2 = null;
+ try {
+ installAppWithStoragePermissions(TEST_APP_SLOW_MOTION);
+
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ ParcelFileDescriptor pfdOriginal1 = open(modernFile, false);
+
+ TranscodeTestUtils.enableTranscodingForPackage(packageName);
+ // App compat takes precedence
+ TranscodeTestUtils.forceEnableAppCompatHevc(packageName);
+
+ Thread.sleep(2000);
+
+ pfdOriginal2 = openFileAs(TEST_APP_SLOW_MOTION, modernFile);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, true);
+ } finally {
+ // Explicitly close PFD otherwise instrumention might crash when test_app is uninstalled
+ if (pfdOriginal2 != null) {
+ pfdOriginal2.close();
+ }
+ modernFile.delete();
+ TranscodeTestUtils.resetAppCompat(packageName);
+ uninstallApp(TEST_APP_HEVC);
+ }
+ }
+
+ @Test
+ public void testAppCompatTranscodeHevc() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ String packageName = TEST_APP_SLOW_MOTION.getPackageName();
+ ParcelFileDescriptor pfdOriginal2 = null;
+ try {
+ installAppWithStoragePermissions(TEST_APP_SLOW_MOTION);
+
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ ParcelFileDescriptor pfdOriginal1 = open(modernFile, false);
+
+ // Transcoding is disabled but app compat enables it (disables hevc support)
+ TranscodeTestUtils.forceDisableAppCompatHevc(packageName);
+
+ pfdOriginal2 = openFileAs(TEST_APP_SLOW_MOTION, modernFile);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal1, pfdOriginal2, false);
+ } finally {
+ // Explicitly close PFD otherwise instrumention might crash when test_app is uninstalled
+ if (pfdOriginal2 != null) {
+ pfdOriginal2.close();
+ }
+ modernFile.delete();
+ TranscodeTestUtils.resetAppCompat(packageName);
+ uninstallApp(TEST_APP_HEVC);
+ }
+ }
+
+ /**
+ * Tests that we never initiate tanscoding for legacy formats.
+ * This test compares the bytes read before and after enabling transcoding for the test app.
+ * @throws Exception
+ */
+ @Test
+ public void testTranscodedNotInitiatedForLegacy_UsingBytesRead() throws Exception {
+ File legacyFile = new File(DIR_CAMERA, LEGACY_FILE_NAME);
+ try {
+ TranscodeTestUtils.stageLegacyVideoFile(legacyFile);
+
+ ParcelFileDescriptor pfdOriginal = open(legacyFile, false);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+ ParcelFileDescriptor pfdTranscoded = open(legacyFile, false);
+
+ assertFileContent(legacyFile, legacyFile, pfdOriginal, pfdTranscoded, true);
+ } finally {
+ legacyFile.delete();
+ }
+ }
+
+ /**
+ * Tests that we never initiate tanscoding for legacy formats.
+ * This test asserts using the time it took to read after enabling transcoding for the test app.
+ * The reason for keeping this check separately (than
+ * {@link TranscodeTest#testTranscodedNotInitiatedForLegacy_UsingTiming()}) is that this
+ * provides a higher level of suret that the timing wasn't favorable because of any caching
+ * after open().
+ * @throws Exception
+ */
+ @Test
+ public void testTranscodedNotInitiatedForLegacy_UsingTiming() throws Exception {
+ File legacyFile = new File(DIR_CAMERA, LEGACY_FILE_NAME);
+ try {
+ TranscodeTestUtils.stageLegacyVideoFile(legacyFile);
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+
+ assertTranscode(legacyFile, false);
+ } finally {
+ legacyFile.delete();
+ }
+ }
+
+ /**
+ * Tests that we don't timeout while transcoding small HEVC videos.
+ * For instance, due to some calculation errors we might incorrectly make timeout to be 0.
+ * We test this by making sure that a small HEVC video (< 1 sec long and < 1Mb size) gets
+ * transcoded.
+ * @throws Exception
+ */
+ @Test
+ public void testNoTranscodeTimeoutForSmallHevcVideos() throws Exception {
+ File modernFile = new File(DIR_CAMERA, SMALL_HEVC_FILE_NAME);
+ try {
+ TranscodeTestUtils.stageSmallHevcVideoFile(modernFile);
+ ParcelFileDescriptor pfdOriginal = open(modernFile, false);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+ ParcelFileDescriptor pfdTranscoded = open(modernFile, false);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal, pfdTranscoded, false);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ /**
+ * Tests that we transcode an HEVC file when a modern app passes the mediaCapabilitiesUid of a
+ * legacy app that cannot handle an HEVC file.
+ */
+ @Test
+ public void testOriginalCallingUid_modernAppPassLegacyAppUid()
+ throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ ParcelFileDescriptor pfdModernApp = null;
+ ParcelFileDescriptor pfdModernAppPassingLegacyUid = null;
+ try {
+ installAppWithStoragePermissions(TEST_APP_SLOW_MOTION);
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ // pfdModernApp is for original content (without transcoding) since this is a modern
+ // app.
+ pfdModernApp = open(modernFile, false);
+
+ // pfdModernAppPassingLegacyUid is for transcoded content since this modern app is
+ // passing the UID of a legacy app capable of handling HEVC files.
+ Bundle bundle = new Bundle();
+ bundle.putInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID,
+ getContext().getPackageManager().getPackageUid(
+ TEST_APP_SLOW_MOTION.getPackageName(), 0));
+ pfdModernAppPassingLegacyUid = open(uri, false, bundle);
+
+ assertTranscode(pfdModernApp, false);
+ assertTranscode(pfdModernAppPassingLegacyUid, true);
+
+ // pfdModernApp and pfdModernAppPassingLegacyUid should be different.
+ assertFileContent(modernFile, modernFile, pfdModernApp, pfdModernAppPassingLegacyUid,
+ false);
+ } finally {
+ if (pfdModernApp != null) {
+ pfdModernApp.close();
+ }
+
+ if (pfdModernAppPassingLegacyUid != null) {
+ pfdModernAppPassingLegacyUid.close();
+ }
+ modernFile.delete();
+ uninstallApp(TEST_APP_SLOW_MOTION);
+ }
+ }
+
+ /**
+ * Tests that we don't transcode an HEVC file when a legacy app passes the mediaCapabilitiesUid
+ * of a modern app that can handle an HEVC file.
+ */
+ @Test
+ public void testOriginalCallingUid_legacyAppPassModernAppUid()
+ throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ ParcelFileDescriptor pfdLegacyApp = null;
+ ParcelFileDescriptor pfdLegacyAppPassingModernUid = null;
+ try {
+ installAppWithStoragePermissions(TEST_APP_HEVC);
+ Uri uri = TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ // pfdLegacyApp is for transcoded content since this is a legacy app.
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+ pfdLegacyApp = open(modernFile, false);
+
+ // pfdLegacyAppPassingModernUid is for original content (without transcoding) since this
+ // legacy app is passing the UID of a modern app capable of handling HEVC files.
+ Bundle bundle = new Bundle();
+ bundle.putInt(MediaStore.EXTRA_MEDIA_CAPABILITIES_UID,
+ getContext().getPackageManager().getPackageUid(TEST_APP_HEVC.getPackageName(),
+ 0));
+ pfdLegacyAppPassingModernUid = open(uri, false, bundle);
+
+ assertTranscode(pfdLegacyApp, true);
+ assertTranscode(pfdLegacyAppPassingModernUid, false);
+
+ // pfdLegacyApp and pfdLegacyAppPassingModernUid should be different.
+ assertFileContent(modernFile, modernFile, pfdLegacyApp, pfdLegacyAppPassingModernUid,
+ false);
+ } finally {
+ if (pfdLegacyApp != null) {
+ pfdLegacyApp.close();
+ }
+
+ if (pfdLegacyAppPassingModernUid != null) {
+ pfdLegacyAppPassingModernUid.close();
+ }
+ modernFile.delete();
+ uninstallApp(TEST_APP_HEVC);
+ }
+ }
+
+ /**
+ * Tests that we return FD of original file from
+ * MediaStore#getOriginalMediaFormatFileDescriptor.
+ * @throws Exception
+ */
+ @Test
+ public void testGetOriginalMediaFormatFileDescriptor_returnsOriginalFileDescriptor()
+ throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ try {
+ TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+
+ ParcelFileDescriptor pfdOriginal = open(modernFile, false);
+
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+ ParcelFileDescriptor pfdTranscoded = open(modernFile, false);
+
+ ParcelFileDescriptor pfdOriginalMediaFormat =
+ MediaStore.getOriginalMediaFormatFileDescriptor(getContext(), pfdTranscoded);
+
+ assertFileContent(modernFile, modernFile, pfdOriginal, pfdOriginalMediaFormat, true);
+ assertFileContent(modernFile, modernFile, pfdTranscoded, pfdOriginalMediaFormat, false);
+ } finally {
+ modernFile.delete();
+ }
+ }
+
+ /**
+ * Tests that we can successfully write to a transcoded file.
+ * We check this by writing something to tanscoded content and then read it back.
+ */
+ @Test
+ public void testWriteSuccessfulToTranscodedContent() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ ParcelFileDescriptor pfdTranscodedContent = null;
+ try {
+ TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+ pfdTranscodedContent = open(modernFile, false);
+
+ // read some bytes from some random offset
+ Random random = new Random(System.currentTimeMillis());
+ int byteCount = 512;
+ int fileOffset = random.nextInt((int) pfdTranscodedContent.getStatSize() - byteCount);
+ byte[] readBytes = TranscodeTestUtils.read(pfdTranscodedContent, byteCount, fileOffset);
+
+ // write the bytes at the same offset after some modification
+ pfdTranscodedContent = open(modernFile, true);
+ byte[] writeBytes = new byte[byteCount];
+ for (int i = 0; i < byteCount; ++i) {
+ writeBytes[i] = (byte) ~readBytes[i];
+ }
+ TranscodeTestUtils.write(pfdTranscodedContent, writeBytes, byteCount, fileOffset);
+
+ // read back the same number of bytes from the same offset
+ readBytes = TranscodeTestUtils.read(pfdTranscodedContent, byteCount, fileOffset);
+
+ // assert that read is same as written
+ assertTrue(Arrays.equals(readBytes, writeBytes));
+ } finally {
+ if (pfdTranscodedContent != null) {
+ pfdTranscodedContent.close();
+ }
+ modernFile.delete();
+ }
+ }
+
+ @Test
+ public void testTranscodeDirectoryNotAccessible() throws Exception {
+ File modernFile = new File(DIR_CAMERA, HEVC_FILE_NAME);
+ ParcelFileDescriptor pfdTranscodedContent = null;
+ try {
+ TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+ pfdTranscodedContent = open(modernFile, false);
+ TranscodeTestUtils.read(pfdTranscodedContent, 512, 0);
+
+ // Transcode directory must be created now.
+ String transcodeDirPath =
+ "/storage/emulated/" + UserHandle.myUserId() + "/.transforms/transcode";
+ File transcodeDir = new File(transcodeDirPath);
+ assertThat(transcodeDir.exists()).isFalse();
+ } finally {
+ if (pfdTranscodedContent != null) {
+ pfdTranscodedContent.close();
+ }
+ modernFile.delete();
+ }
+ }
+
+ @Test
+ public void testTranscodeMultipleFilesConcurrently_mediumDurationMediumVolume() throws Exception {
+ ModernFileOpenerThread[] modernFileOpenerThreads = new ModernFileOpenerThread[20];
+ for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+ modernFileOpenerThreads[i] = new ModernFileOpenerThread(
+ ModernFileOpenerThread.FileDurationSeconds.TWENTIES);
+ }
+
+ for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+ modernFileOpenerThreads[i].start();
+ }
+
+ for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+ modernFileOpenerThreads[i].join();
+ if (modernFileOpenerThreads[i].mException != null) {
+ throw new Exception("Failed ModernFileOpenerThread - " + i + ": "
+ + modernFileOpenerThreads[i].mException.getMessage(),
+ modernFileOpenerThreads[i].mException);
+ }
+ }
+ }
+
+ @Test
+ public void testTranscodeMultipleFilesConcurrently_lowDurationHighVolume() throws Exception {
+ ModernFileOpenerThread[] modernFileOpenerThreads = new ModernFileOpenerThread[100];
+ for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+ modernFileOpenerThreads[i] = new ModernFileOpenerThread(
+ ModernFileOpenerThread.FileDurationSeconds.FEW);
+ }
+
+ for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+ modernFileOpenerThreads[i].start();
+ }
+
+ for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+ modernFileOpenerThreads[i].join();
+ if (modernFileOpenerThreads[i].mException != null) {
+ throw new Exception("Failed ModernFileOpenerThread - " + i + ": "
+ + modernFileOpenerThreads[i].mException.getMessage(),
+ modernFileOpenerThreads[i].mException);
+ }
+ }
+ }
+
+ @Test
+ public void testTranscodeMultipleFilesConcurrently_longDurationLowVolume() throws Exception {
+ ModernFileOpenerThread[] modernFileOpenerThreads = new ModernFileOpenerThread[5];
+ for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+ modernFileOpenerThreads[i] = new ModernFileOpenerThread(
+ ModernFileOpenerThread.FileDurationSeconds.HUNDRED);
+ }
+
+ for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+ modernFileOpenerThreads[i].start();
+ }
+
+ for (int i = 0; i < modernFileOpenerThreads.length; ++i) {
+ modernFileOpenerThreads[i].join();
+ if (modernFileOpenerThreads[i].mException != null) {
+ throw new Exception("Failed ModernFileOpenerThread - " + i + ": "
+ + modernFileOpenerThreads[i].mException.getMessage(),
+ modernFileOpenerThreads[i].mException);
+ }
+ }
+ }
+
+ private static final class ModernFileOpenerThread extends Thread {
+ private final FileDurationSeconds mFileDurationSeconds;
+ Throwable mException;
+
+ ModernFileOpenerThread(FileDurationSeconds fileDurationSeconds) {
+ mFileDurationSeconds = fileDurationSeconds;
+ }
+
+ @Override
+ public void run() {
+ try {
+ openFile();
+ } catch (Exception exception) {
+ mException = exception;
+ }
+ }
+
+ private void openFile() throws Exception {
+ String fileName = "TranscodeTestHEVC_" + System.nanoTime() + ".mp4";
+ File modernFile = new File(DIR_CAMERA, fileName);
+ ParcelFileDescriptor pfdTranscoded = null;
+ try {
+ switch (mFileDurationSeconds) {
+ case FEW:
+ TranscodeTestUtils.stageHEVCVideoFile(modernFile);
+ break;
+ case TWENTIES:
+ TranscodeTestUtils.stageMediumHevcVideoFile(modernFile);
+ break;
+ case HUNDRED:
+ TranscodeTestUtils.stageLongHevcVideoFile(modernFile);
+ break;
+ default:
+ throw new IllegalStateException(
+ "Unknown mFileDurationSeconds: " + mFileDurationSeconds);
+ }
+ TranscodeTestUtils.enableTranscodingForPackage(getContext().getPackageName());
+ pfdTranscoded = open(modernFile, false);
+ assertTranscode(pfdTranscoded, true);
+ } finally {
+ if (pfdTranscoded != null) {
+ pfdTranscoded.close();
+ }
+ modernFile.delete();
+ }
+ }
+
+ enum FileDurationSeconds {
+ FEW,
+ TWENTIES,
+ HUNDRED
+ }
+ }
+}
diff --git a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestConstants.java b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestConstants.java
new file mode 100644
index 0000000..546e94f
--- /dev/null
+++ b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestConstants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediaprovidertranscode.cts;
+
+public final class TranscodeTestConstants {
+ private TranscodeTestConstants() {
+ }
+
+ public static final String INTENT_QUERY_TYPE =
+ "android.mediaprovidertranscode.cts.query_type";
+ public static final String INTENT_EXTRA_CALLING_PKG =
+ "android.mediaprovidertranscode.cts.calling_pkg";
+ public static final String INTENT_EXTRA_PATH = "android.mediaprovidertranscode.cts.path";
+ public static final String OPEN_FILE_QUERY = "android.mediaprovidertranscode.cts.open_file";
+}
diff --git a/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java
new file mode 100644
index 0000000..200dfa3
--- /dev/null
+++ b/tests/MediaProviderTranscode/src/android/mediaprovidertranscode/cts/TranscodeTestUtils.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediaprovidertranscode.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.INTENT_EXTRA_CALLING_PKG;
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.INTENT_EXTRA_PATH;
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.OPEN_FILE_QUERY;
+import static android.mediaprovidertranscode.cts.TranscodeTestConstants.INTENT_QUERY_TYPE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.UiAutomation;
+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.Bundle;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+
+import com.google.common.io.ByteStreams;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Supplier;
+
+public class TranscodeTestUtils {
+ private static final String TAG = "TranscodeTestUtils";
+
+ private static final long POLLING_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20);
+ private static final long POLLING_SLEEP_MILLIS = 100;
+
+ public static Uri stageHEVCVideoFile(File videoFile) throws IOException {
+ return stageVideoFile(videoFile, R.raw.testvideo_HEVC);
+ }
+
+ public static Uri stageSmallHevcVideoFile(File videoFile) throws IOException {
+ return stageVideoFile(videoFile, R.raw.testVideo_HEVC_small);
+ }
+
+ public static Uri stageMediumHevcVideoFile(File videoFile) throws IOException {
+ return stageVideoFile(videoFile, R.raw.testVideo_HEVC_medium);
+ }
+
+ public static Uri stageLongHevcVideoFile(File videoFile) throws IOException {
+ return stageVideoFile(videoFile, R.raw.testVideo_HEVC_long);
+ }
+
+ public static Uri stageLegacyVideoFile(File videoFile) throws IOException {
+ return stageVideoFile(videoFile, R.raw.testVideo_Legacy);
+ }
+
+ private static Uri stageVideoFile(File videoFile, int resourceId) throws IOException {
+ if (!videoFile.getParentFile().exists()) {
+ assertTrue(videoFile.getParentFile().mkdirs());
+ }
+ try (InputStream in =
+ getContext().getResources().openRawResource(resourceId);
+ FileOutputStream out = new FileOutputStream(videoFile)) {
+ FileUtils.copy(in, out);
+ // Sync file to disk to ensure file is fully written to the lower fs before scanning
+ // Otherwise, media provider might try to read the file on the lower fs and not see
+ // the fully written bytes
+ out.getFD().sync();
+ }
+ return MediaStore.scanFile(getContext().getContentResolver(), videoFile);
+ }
+
+ public static ParcelFileDescriptor open(File file, boolean forWrite) throws Exception {
+ return ParcelFileDescriptor.open(file, forWrite ? ParcelFileDescriptor.MODE_READ_WRITE
+ : ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+
+ public static ParcelFileDescriptor open(Uri uri, boolean forWrite, Bundle bundle)
+ throws Exception {
+ ContentResolver resolver = getContext().getContentResolver();
+ if (bundle == null) {
+ return resolver.openFileDescriptor(uri, forWrite ? "rw" : "r");
+ } else {
+ return resolver.openTypedAssetFileDescriptor(uri, "*/*", bundle)
+ .getParcelFileDescriptor();
+ }
+ }
+
+ static byte[] read(ParcelFileDescriptor parcelFileDescriptor, int byteCount, int fileOffset)
+ throws Exception {
+ assertThat(byteCount).isGreaterThan(-1);
+ assertThat(fileOffset).isGreaterThan(-1);
+
+ Os.lseek(parcelFileDescriptor.getFileDescriptor(), fileOffset, OsConstants.SEEK_SET);
+
+ byte[] bytes = new byte[byteCount];
+ int numBytesRead = Os.read(parcelFileDescriptor.getFileDescriptor(), bytes,
+ 0 /* byteOffset */, byteCount);
+ assertThat(numBytesRead).isGreaterThan(-1);
+ return bytes;
+ }
+
+ static void write(ParcelFileDescriptor parcelFileDescriptor, byte[] bytes, int byteCount,
+ int fileOffset) throws Exception {
+ assertThat(byteCount).isGreaterThan(-1);
+ assertThat(fileOffset).isGreaterThan(-1);
+
+ Os.lseek(parcelFileDescriptor.getFileDescriptor(), fileOffset, OsConstants.SEEK_SET);
+
+ int numBytesWritten = Os.write(parcelFileDescriptor.getFileDescriptor(), bytes,
+ 0 /* byteOffset */, byteCount);
+ assertThat(numBytesWritten).isNotEqualTo(-1);
+ assertThat(numBytesWritten).isEqualTo(byteCount);
+ }
+
+ public static void enableSeamlessTranscoding() throws Exception {
+ // This is required so that MediaProvider handles device config changes
+ executeShellCommand("setprop sys.fuse.transcode_debug true");
+ // This is required so that setprop changes take precedence over device_config changes
+ executeShellCommand("setprop persist.sys.fuse.transcode_user_control true");
+ executeShellCommand("setprop persist.sys.fuse.transcode_enabled true");
+ executeShellCommand("setprop persist.sys.fuse.transcode_default false");
+ }
+
+ public static void disableSeamlessTranscoding() throws Exception {
+ executeShellCommand("setprop sys.fuse.transcode_debug false");
+ executeShellCommand("setprop persist.sys.fuse.transcode_user_control true");
+ executeShellCommand("setprop persist.sys.fuse.transcode_enabled false");
+ executeShellCommand("setprop persist.sys.fuse.transcode_default false");
+ disableTranscodingForAllPackages();
+ }
+
+ public static void enableTranscodingForPackage(String packageName) throws Exception {
+ executeShellCommand("device_config put storage_native_boot transcode_compat_manifest "
+ + packageName + ",0");
+ }
+
+ public static void forceEnableAppCompatHevc(String packageName) throws IOException {
+ final String command = "am compat enable 174228127 " + packageName;
+ executeShellCommand(command);
+ }
+
+ public static void forceDisableAppCompatHevc(String packageName) throws IOException {
+ final String command = "am compat enable 174227820 " + packageName;
+ executeShellCommand(command);
+ }
+
+ public static void resetAppCompat(String packageName) throws IOException {
+ final String command = "am compat reset-all " + packageName;
+ executeShellCommand(command);
+ }
+
+ public static void disableTranscodingForAllPackages() throws IOException {
+ executeShellCommand("device_config delete storage_native_boot transcode_compat_manifest");
+ }
+
+ /**
+ * Executes a shell command.
+ */
+ public static String executeShellCommand(String command) throws IOException {
+ int attempt = 0;
+ while (attempt++ < 5) {
+ try {
+ return executeShellCommandInternal(command);
+ } catch (InterruptedIOException e) {
+ // Hmm, we had trouble executing the shell command; the best we
+ // can do is try again a few more times
+ Log.v(TAG, "Trouble executing " + command + "; trying again", e);
+ }
+ }
+ throw new IOException("Failed to execute " + command);
+ }
+
+ private static String executeShellCommandInternal(String cmd) throws IOException {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try (FileInputStream output = new FileInputStream(
+ uiAutomation.executeShellCommand(cmd).getFileDescriptor())) {
+ return new String(ByteStreams.toByteArray(output));
+ }
+ }
+
+ /**
+ * Polls for external storage to be mounted.
+ */
+ public static void pollForExternalStorageState() throws Exception {
+ pollForCondition(
+ () -> Environment.getExternalStorageState(Environment.getExternalStorageDirectory())
+ .equals(Environment.MEDIA_MOUNTED),
+ "Timed out while waiting for ExternalStorageState to be MEDIA_MOUNTED");
+ }
+
+ private static void pollForCondition(Supplier<Boolean> condition, String errorMessage)
+ throws Exception {
+ for (int i = 0; i < POLLING_TIMEOUT_MILLIS / POLLING_SLEEP_MILLIS; i++) {
+ if (condition.get()) {
+ return;
+ }
+ Thread.sleep(POLLING_SLEEP_MILLIS);
+ }
+ throw new TimeoutException(errorMessage);
+ }
+
+ public static void grantPermission(String packageName, String permission) {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ uiAutomation.adoptShellPermissionIdentity("android.permission.GRANT_RUNTIME_PERMISSIONS");
+ try {
+ uiAutomation.grantRuntimePermission(packageName, permission);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
+ * Polls until we're granted or denied a given permission.
+ */
+ public static void pollForPermission(String perm, boolean granted) throws Exception {
+ pollForCondition(() -> granted == checkPermissionAndAppOp(perm),
+ "Timed out while waiting for permission " + perm + " to be "
+ + (granted ? "granted" : "revoked"));
+ }
+
+
+ /**
+ * Checks if the given {@code permission} is granted and corresponding AppOp is MODE_ALLOWED.
+ */
+ private static boolean checkPermissionAndAppOp(String permission) {
+ final int pid = Os.getpid();
+ final int uid = Os.getuid();
+ final Context context = getContext();
+ final String packageName = context.getPackageName();
+ if (context.checkPermission(permission, pid, uid) != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+
+ final String op = AppOpsManager.permissionToOp(permission);
+ // No AppOp associated with the given permission, skip AppOp check.
+ if (op == null) {
+ return true;
+ }
+
+ final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
+ try {
+ appOps.checkPackage(uid, packageName);
+ } catch (SecurityException e) {
+ return false;
+ }
+
+ return appOps.unsafeCheckOpNoThrow(op, uid, packageName) == AppOpsManager.MODE_ALLOWED;
+ }
+
+ /**
+ * Installs a {@link TestApp} and grants it storage permissions.
+ */
+ public static void installAppWithStoragePermissions(TestApp testApp)
+ throws Exception {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ final String packageName = testApp.getPackageName();
+ uiAutomation.adoptShellPermissionIdentity(
+ Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES);
+ if (InstallUtils.getInstalledVersion(packageName) != -1) {
+ Uninstall.packages(packageName);
+ }
+ Install.single(testApp).commit();
+ assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1);
+
+ grantPermission(packageName, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ grantPermission(packageName, Manifest.permission.READ_EXTERNAL_STORAGE);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
+ * Uninstalls a {@link TestApp}.
+ */
+ public static void uninstallApp(TestApp testApp) throws Exception {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ final String packageName = testApp.getPackageName();
+ uiAutomation.adoptShellPermissionIdentity(Manifest.permission.DELETE_PACKAGES);
+
+ Uninstall.packages(packageName);
+ assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(-1);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception occurred while uninstalling app: " + testApp, e);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
+ * Makes the given {@code testApp} open a file for read or write.
+ *
+ * <p>This method drops shell permission identity.
+ */
+ public static ParcelFileDescriptor openFileAs(TestApp testApp, File dirPath)
+ throws Exception {
+ String actionName = getContext().getPackageName() + ".open_file";
+ Bundle bundle = getFromTestApp(testApp, dirPath.getPath(), actionName);
+ return getContext().getContentResolver().openFileDescriptor(
+ bundle.getParcelable(actionName), "rw");
+ }
+
+ /**
+ * <p>This method drops shell permission identity.
+ */
+ private static Bundle getFromTestApp(TestApp testApp, String dirPath, String actionName)
+ throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Bundle[] bundle = new Bundle[1];
+ BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ bundle[0] = intent.getExtras();
+ latch.countDown();
+ }
+ };
+
+ sendIntentToTestApp(testApp, dirPath, actionName, broadcastReceiver, latch);
+ return bundle[0];
+ }
+
+ /**
+ * <p>This method drops shell permission identity.
+ */
+ private static void sendIntentToTestApp(TestApp testApp, String dirPath, String actionName,
+ BroadcastReceiver broadcastReceiver, CountDownLatch latch) throws Exception {
+ final String packageName = testApp.getPackageName();
+ forceStopApp(packageName);
+ // Register broadcast receiver
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(actionName);
+ intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
+ getContext().registerReceiver(broadcastReceiver, intentFilter);
+
+ // Launch the test app.
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setPackage(packageName);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(INTENT_QUERY_TYPE, actionName);
+ intent.putExtra(INTENT_EXTRA_CALLING_PKG, getContext().getPackageName());
+ intent.putExtra(INTENT_EXTRA_PATH, dirPath);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ getContext().startActivity(intent);
+ if (!latch.await(POLLING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
+ final String errorMessage = "Timed out while waiting to receive " + actionName
+ + " intent from " + packageName;
+ throw new TimeoutException(errorMessage);
+ }
+ getContext().unregisterReceiver(broadcastReceiver);
+ }
+
+ /**
+ * <p>This method drops shell permission identity.
+ */
+ private static void forceStopApp(String packageName) throws Exception {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity(Manifest.permission.FORCE_STOP_PACKAGES);
+
+ getContext().getSystemService(ActivityManager.class).forceStopPackage(packageName);
+ Thread.sleep(1000);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ public static void assertFileContent(File file1, File file2, ParcelFileDescriptor pfd1,
+ ParcelFileDescriptor pfd2, boolean assertSame) throws Exception {
+ final int len = 1024;
+ byte[] bytes1;
+ byte[] bytes2;
+ int size1 = 0;
+ int size2 = 0;
+
+ boolean isSame = true;
+ do {
+ bytes1 = new byte[len];
+ bytes2 = new byte[len];
+
+ size1 = Os.read(pfd1.getFileDescriptor(), bytes1, 0, len);
+ size2 = Os.read(pfd2.getFileDescriptor(), bytes2, 0, len);
+
+ assertTrue(size1 >= 0);
+ assertTrue(size2 >= 0);
+
+ isSame = (size1 == size2) && Arrays.equals(bytes1, bytes2);
+ if (!isSame) {
+ break;
+ }
+ } while (size1 > 0 && size2 > 0);
+
+ String message = String.format("Files: %s and %s. isSame=%b. assertSame=%s",
+ file1, file2, isSame, assertSame);
+ assertEquals(message, isSame, assertSame);
+ }
+
+ public static void assertTranscode(Uri uri, boolean transcode) throws Exception {
+ long start = SystemClock.elapsedRealtimeNanos();
+ assertTranscode(open(uri, true, null /* bundle */), transcode);
+ }
+
+ public static void assertTranscode(File file, boolean transcode) throws Exception {
+ assertTranscode(open(file, false), transcode);
+ }
+
+ public static void assertTranscode(ParcelFileDescriptor pfd, boolean transcode)
+ throws Exception {
+ long start = SystemClock.elapsedRealtimeNanos();
+ assertEquals(10, Os.pread(pfd.getFileDescriptor(), new byte[10], 0, 10, 0));
+ long end = SystemClock.elapsedRealtimeNanos();
+ long readDuration = end - start;
+
+ // With transcoding read(2) > 100ms (usually > 1s)
+ // Without transcoding read(2) < 10ms (usually < 1ms)
+ String message = "readDuration=" + readDuration + "ns";
+ if (transcode) {
+ assertTrue(message, readDuration > TimeUnit.MILLISECONDS.toNanos(100));
+ } else {
+ assertTrue(message, readDuration < TimeUnit.MILLISECONDS.toNanos(10));
+ }
+ }
+}
diff --git a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index a8d93b5..b7022c2 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -116,24 +116,17 @@
assertTrue(mDevicePolicyManager.isAdminActive(mComponent));
}
- public void testSetGetNetworkSlicingEnabled() {
+ public void testSetGetEnterpriseNetworkPreferenceEnabled() {
if (!mDeviceAdmin) {
- Log.w(TAG, "Skipping SetGetNetworkSlicingEnabled");
+ Log.w(TAG, "Skipping testSetGetEnterpriseNetworkPreferenceEnabled");
return;
}
-
-
try {
mDevicePolicyManager.clearProfileOwner(DeviceAdminInfoTest.getProfileOwnerComponent());
assertThrows(SecurityException.class,
- () -> mDevicePolicyManager.setNetworkSlicingEnabled(true));
-
+ () -> mDevicePolicyManager.setEnterpriseNetworkPreferenceEnabled(true));
assertThrows(SecurityException.class,
- () -> mDevicePolicyManager.isNetworkSlicingEnabled());
-
- assertThrows(SecurityException.class,
- () -> mDevicePolicyManager.isNetworkSlicingEnabledForUser(
- Process.myUserHandle()));
+ () -> mDevicePolicyManager.isEnterpriseNetworkPreferenceEnabled());
} catch (SecurityException se) {
Log.w(TAG, "Test is not a profile owner and there is no need to clear.");
} finally {
@@ -141,7 +134,6 @@
"dpm set-profile-owner --user cur "
+ DeviceAdminInfoTest.getProfileOwnerComponent().flattenToString());
}
-
}
public void testKeyguardDisabledFeatures() {
diff --git a/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt b/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt
index 4197760..e511936 100644
--- a/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt
+++ b/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt
@@ -27,7 +27,7 @@
*/
class RichNotificationActivity : Activity() {
companion object {
- const val NOTIFICATION_CHANNEL_ID = "NotificationManagerTest"
+ const val NOTIFICATION_MANAGER_CHANNEL_ID = "NotificationManagerTest"
const val EXTRA_ACTION = "action"
const val ACTION_SEND_7 = "send-7"
const val ACTION_SEND_8 = "send-8"
@@ -35,15 +35,15 @@
const val ACTION_CANCEL_8 = "cancel-8"
}
- enum class NotificationPreset(val id: Int) {
- Preset7(7),
- Preset8(8);
+ enum class NotificationPreset(val id: Int, val channelId: String) {
+ Preset7(7, NOTIFICATION_MANAGER_CHANNEL_ID),
+ Preset8(8, NOTIFICATION_MANAGER_CHANNEL_ID);
fun build(context: Context): Notification {
val extras = Bundle()
extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI,
"content://com.android.test.notificationprovider.provider/background$id.png")
- return Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
+ return Notification.Builder(context, NOTIFICATION_MANAGER_CHANNEL_ID)
.setContentTitle("Rich Notification #$id")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.addExtras(extras)
@@ -59,11 +59,7 @@
ACTION_SEND_8 -> sendNotification(NotificationPreset.Preset8)
ACTION_CANCEL_7 -> cancelNotification(NotificationPreset.Preset7)
ACTION_CANCEL_8 -> cancelNotification(NotificationPreset.Preset8)
- else -> {
- // reset both
- cancelNotification(NotificationPreset.Preset7)
- cancelNotification(NotificationPreset.Preset8)
- }
+ else -> NotificationPreset.values().forEach(::cancelNotification)
}
finish()
}
@@ -71,8 +67,8 @@
private val notificationManager by lazy { getSystemService(NotificationManager::class.java)!! }
private fun sendNotification(preset: NotificationPreset) {
- notificationManager.createNotificationChannel(NotificationChannel(NOTIFICATION_CHANNEL_ID,
- "Notifications", NotificationManager.IMPORTANCE_DEFAULT))
+ notificationManager.createNotificationChannel(NotificationChannel(preset.channelId,
+ "${preset.channelId} Notifications", NotificationManager.IMPORTANCE_DEFAULT))
notificationManager.notify(preset.id, preset.build(this))
}
diff --git a/tests/app/app/Android.bp b/tests/app/app/Android.bp
index 0a7de18..44c365c 100644
--- a/tests/app/app/Android.bp
+++ b/tests/app/app/Android.bp
@@ -46,6 +46,7 @@
"cts",
"general-tests",
],
+ additional_manifests: ["ProviderAndroidManifest.xml"],
platform_apis: true,
}
diff --git a/tests/app/app/ProviderAndroidManifest.xml b/tests/app/app/ProviderAndroidManifest.xml
new file mode 100644
index 0000000..2aef16d
--- /dev/null
+++ b/tests/app/app/ProviderAndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.stubs">
+
+ <application>
+
+ <provider
+ android:name="android.app.stubs.AssetFileProvider"
+ android:authorities="android.app.stubs.assets"
+ android:exported="false"
+ android:grantUriPermissions="true"
+ />
+
+ </application>
+
+</manifest>
diff --git a/tests/app/app/assets/picture_400_by_300.png b/tests/app/app/assets/picture_400_by_300.png
new file mode 100644
index 0000000..cc3283c
--- /dev/null
+++ b/tests/app/app/assets/picture_400_by_300.png
Binary files differ
diff --git a/tests/app/app/src/android/app/stubs/AssetFileProvider.kt b/tests/app/app/src/android/app/stubs/AssetFileProvider.kt
new file mode 100644
index 0000000..7b3fb67
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/AssetFileProvider.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ContentProvider
+import android.content.ContentValues
+import android.content.res.AssetFileDescriptor
+import android.database.Cursor
+import android.net.Uri
+
+class AssetFileProvider : ContentProvider() {
+ override fun onCreate() = true
+
+ override fun openAssetFile(uri: Uri, mode: String): AssetFileDescriptor? {
+ val assets = context?.assets
+ val filename = uri.lastPathSegment
+ if (mode == "r" && assets != null && filename != null) {
+ return assets.openFd(filename)
+ }
+ return super.openAssetFile(uri, mode)
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array<String>?,
+ selection: String?,
+ selectionArgs: Array<String>?,
+ sortOrder: String?
+ ): Cursor = throw UnsupportedOperationException()
+
+ override fun getType(uri: Uri): String? = null
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri =
+ throw UnsupportedOperationException()
+
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int =
+ throw UnsupportedOperationException()
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<String>?
+ ): Int = throw UnsupportedOperationException()
+}
\ No newline at end of file
diff --git a/tests/app/app/src/android/app/stubs/LocalForegroundService.java b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
index 76d2e45..25e77b0 100644
--- a/tests/app/app/src/android/app/stubs/LocalForegroundService.java
+++ b/tests/app/app/src/android/app/stubs/LocalForegroundService.java
@@ -36,7 +36,7 @@
private static final String TAG = "LocalForegroundService";
protected static final String EXTRA_COMMAND = "LocalForegroundService.command";
- private static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
+ public static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
public static String ACTION_START_FGS_RESULT =
"android.app.stubs.LocalForegroundService.RESULT";
diff --git a/tests/app/app/src/android/app/stubs/TestNotificationListener.java b/tests/app/app/src/android/app/stubs/TestNotificationListener.java
index 14d5416..eabde5c 100644
--- a/tests/app/app/src/android/app/stubs/TestNotificationListener.java
+++ b/tests/app/app/src/android/app/stubs/TestNotificationListener.java
@@ -21,6 +21,8 @@
import android.service.notification.StatusBarNotification;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
public class TestNotificationListener extends NotificationListenerService {
public static final String TAG = "TestNotificationListener";
@@ -30,7 +32,7 @@
private ArrayList<String> mTestPackages = new ArrayList<>();
public ArrayList<StatusBarNotification> mPosted = new ArrayList<>();
- public ArrayList<StatusBarNotification> mRemoved = new ArrayList<>();
+ public Map<String, Integer> mRemoved = new HashMap<>();
public RankingMap mRankingMap;
/**
@@ -105,10 +107,11 @@
}
@Override
- public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+ int reason) {
if (sbn == null || !mTestPackages.contains(sbn.getPackageName())) { return; }
mRankingMap = rankingMap;
- mRemoved.add(sbn);
+ mRemoved.put(sbn.getKey(), reason);
}
@Override
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index 22a47bd..3993cd2 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -53,6 +53,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
import com.android.compatibility.common.util.SystemUtil;
@@ -1152,6 +1153,112 @@
}
/**
+ * Test a FGS can start from BG if the process had a visible activity recently.
+ */
+ @LargeTest
+ @Test
+ public void testVisibleActivityGracePeriod() throws Exception {
+ ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+ PACKAGE_NAME_APP2, 0);
+ WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
+ WAITFOR_MSEC);
+ final String namespaceActivityManager = "activity_manager";
+ final String keyFgToBgFgsGraceDuration = "fg_to_bg_fgs_grace_duration";
+ final long[] curFgToBgFgsGraceDuration = {-1};
+ try {
+ // Enable the FGS background startForeground() restriction.
+ enableFgsRestriction(true, true, null);
+ // Allow bg actvity start from APP1.
+ allowBgActivityStart(PACKAGE_NAME_APP1, true);
+
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ curFgToBgFgsGraceDuration[0] = DeviceConfig.getInt(
+ namespaceActivityManager,
+ keyFgToBgFgsGraceDuration, -1);
+ DeviceConfig.setProperty(namespaceActivityManager,
+ keyFgToBgFgsGraceDuration,
+ Long.toString(WAITFOR_MSEC), false);
+ });
+
+ testVisibleActivityGracePeriodInternal(uid2Watcher, "KEYCODE_HOME");
+ testVisibleActivityGracePeriodInternal(uid2Watcher, "KEYCODE_BACK");
+ } finally {
+ uid2Watcher.finish();
+ // Remove package from AllowList.
+ allowBgActivityStart(PACKAGE_NAME_APP1, false);
+ if (curFgToBgFgsGraceDuration[0] >= 0) {
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ DeviceConfig.setProperty(namespaceActivityManager,
+ keyFgToBgFgsGraceDuration,
+ Long.toString(curFgToBgFgsGraceDuration[0]), false);
+ });
+ } else {
+ CtsAppTestUtils.executeShellCmd(mInstrumentation,
+ "device_config delete " + namespaceActivityManager
+ + " " + keyFgToBgFgsGraceDuration);
+ }
+ }
+ }
+
+ private void testVisibleActivityGracePeriodInternal(WatchUidRunner uidWatcher, String keyCode)
+ throws Exception {
+ testVisibleActivityGracePeriodInternal(uidWatcher, keyCode, null,
+ () -> uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_FG_SERVICE), true);
+
+ testVisibleActivityGracePeriodInternal(uidWatcher, keyCode,
+ () -> SystemClock.sleep(WAITFOR_MSEC + 2000), // Wait for the grace period to expire
+ () -> {
+ try {
+ uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+ WatchUidRunner.STATE_FG_SERVICE);
+ fail("Service should not enter foreground service state");
+ } catch (Exception e) {
+ // Expected.
+ }
+ }, false);
+ }
+
+ private void testVisibleActivityGracePeriodInternal(WatchUidRunner uidWatcher,
+ String keyCode, Runnable prep, Runnable verifier, boolean stopFgs) throws Exception {
+ // Put APP2 in TOP state.
+ CommandReceiver.sendCommand(mContext,
+ CommandReceiver.COMMAND_START_ACTIVITY,
+ PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+ uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP);
+
+ // Take a nap to wait for the UI to settle down.
+ SystemClock.sleep(2000);
+
+ // Now inject key event.
+ CtsAppTestUtils.executeShellCmd(mInstrumentation, "input keyevent " + keyCode);
+
+ // It should go to the cached state.
+ uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+
+ if (prep != null) {
+ prep.run();
+ }
+
+ // Start FGS from APP2.
+ CommandReceiver.sendCommand(mContext,
+ CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+ PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
+
+ if (verifier != null) {
+ verifier.run();
+ }
+
+ if (stopFgs) {
+ // Stop the FGS.
+ CommandReceiver.sendCommand(mContext,
+ CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+ PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
+ uidWatcher.waitFor(WatchUidRunner.CMD_CACHED, null);
+ }
+ }
+
+ /**
* Turn on the FGS BG-launch restriction. DeviceConfig can turn on restriction on the whole
* device (across all apps). AppCompat can turn on restriction on a single app package.
* @param enable true to turn on restriction, false to turn off.
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index a37144d..d983e4c 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -568,6 +568,20 @@
fail("Couldn't find posted notification with id= " + id);
}
+ private int getCancellationReason(String key) {
+ for (int tries = 3; tries-- > 0; ) {
+ if (mListener.mRemoved.containsKey(key)) {
+ return mListener.mRemoved.get(key);
+ }
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ex) {
+ // pass
+ }
+ }
+ return -1;
+ }
+
private boolean checkNotificationExistence(int id, boolean shouldExist) {
// notification is a bit asynchronous so it may take a few ms to appear in
// getActiveNotifications()
@@ -2008,7 +2022,7 @@
.bigLargeIcon(
Icon.createWithResource(getContext(), R.drawable.icon_blue))
.setSummaryText("summary")
- .bigPictureContentDescription("content description"))
+ .setContentDescription("content description"))
.build();
mNotificationManager.notify(id, notification);
@@ -4254,6 +4268,21 @@
}
}
+ public void testChannelDeletion_cancelReason() throws Exception {
+ setUpNotifListener();
+
+ sendNotification(566, R.drawable.black);
+
+ Thread.sleep(500); // wait for notification listener to receive notification
+ assertEquals(1, mListener.mPosted.size());
+ String key = mListener.mPosted.get(0).getKey();
+
+ mNotificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+
+ assertEquals(NotificationListenerService.REASON_CHANNEL_REMOVED,
+ getCancellationReason(key));
+ }
+
private static class EventCallback extends Handler {
private static final int BROADCAST_RECEIVED = 1;
private static final int SERVICE_STARTED = 2;
diff --git a/tests/app/src/android/app/cts/NotificationTemplateTest.kt b/tests/app/src/android/app/cts/NotificationTemplateTest.kt
index b294b6e..954a6c5 100644
--- a/tests/app/src/android/app/cts/NotificationTemplateTest.kt
+++ b/tests/app/src/android/app/cts/NotificationTemplateTest.kt
@@ -25,6 +25,7 @@
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.drawable.Icon
+import android.net.Uri
import android.view.View
import android.widget.ImageView
import android.widget.TextView
@@ -66,6 +67,22 @@
}
}
+ fun testWideIcon_inCollapsedState_canShowUriIcon() {
+ val uri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png")
+ val icon = Icon.createWithContentUri(uri)
+ val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_media_play)
+ .setContentTitle("Title")
+ .setLargeIcon(icon)
+ .createContentView()
+ checkIconView(views) { iconView ->
+ assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(iconView.width.toFloat())
+ .isWithin(1f)
+ .of((iconView.height * 4 / 3).toFloat())
+ }
+ }
+
fun testWideIcon_inCollapsedState_neverNarrowerThanSquare() {
val icon = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
val views = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
@@ -217,6 +234,74 @@
}
}
+ fun testBigPictureStyle_populatesExtrasCompatibly() {
+ val bitmap = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
+ val uri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png")
+ val iconWithUri = Icon.createWithContentUri(uri)
+ val iconWithBitmap = Icon.createWithBitmap(bitmap)
+ val style = Notification.BigPictureStyle()
+ val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_media_play)
+ .setContentTitle("Title")
+ .setStyle(style)
+
+ style.bigPicture(bitmap)
+ builder.build().let {
+ assertThat(it.extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE))
+ .isSameInstanceAs(bitmap)
+ assertThat(it.extras.get(Notification.EXTRA_PICTURE_ICON)).isNull()
+ }
+
+ style.bigPicture(iconWithUri)
+ builder.build().let {
+ assertThat(it.extras.get(Notification.EXTRA_PICTURE)).isNull()
+ assertThat(it.extras.getParcelable<Icon>(Notification.EXTRA_PICTURE_ICON))
+ .isSameInstanceAs(iconWithUri)
+ }
+
+ style.bigPicture(iconWithBitmap)
+ builder.build().let {
+ assertThat(it.extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE))
+ .isSameInstanceAs(bitmap)
+ assertThat(it.extras.get(Notification.EXTRA_PICTURE_ICON)).isNull()
+ }
+ }
+
+ fun testBigPictureStyle_bigPictureUriIcon() {
+ val pictureUri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png")
+ val pictureIcon = Icon.createWithContentUri(pictureUri)
+ val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_media_play)
+ .setContentTitle("Title")
+ .setStyle(Notification.BigPictureStyle().bigPicture(pictureIcon))
+ checkViews(builder.createBigContentView()) {
+ val pictureView = requireViewByIdName<ImageView>("big_picture")
+ assertThat(pictureView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(pictureView.drawable.intrinsicWidth).isEqualTo(400)
+ assertThat(pictureView.drawable.intrinsicHeight).isEqualTo(300)
+ }
+ }
+
+ fun testPromoteBigPicture_withBigPictureUriIcon() {
+ val pictureUri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png")
+ val pictureIcon = Icon.createWithContentUri(pictureUri)
+ val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_media_play)
+ .setContentTitle("Title")
+ .setStyle(Notification.BigPictureStyle()
+ .bigPicture(pictureIcon)
+ .showBigPictureWhenCollapsed(true)
+ )
+ checkIconView(builder.createContentView()) { iconView ->
+ assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(iconView.width.toFloat())
+ .isWithin(1f)
+ .of((iconView.height * 4 / 3).toFloat())
+ assertThat(iconView.drawable.intrinsicWidth).isEqualTo(400)
+ assertThat(iconView.drawable.intrinsicHeight).isEqualTo(300)
+ }
+ }
+
fun testPromoteBigPicture_withoutLargeIcon() {
val picture = Bitmap.createBitmap(40, 30, Bitmap.Config.ARGB_8888)
val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
@@ -295,6 +380,25 @@
assertThat(iconView.drawable.intrinsicWidth).isEqualTo(80)
assertThat(iconView.drawable.intrinsicHeight).isEqualTo(75)
}
+ assertThat(builder.build().extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE))
+ .isSameInstanceAs(picture)
+ }
+
+ fun testBigPicture_withBigLargeIcon_withContentUri() {
+ val iconUri = Uri.parse("content://android.app.stubs.assets/picture_400_by_300.png")
+ val icon = Icon.createWithContentUri(iconUri)
+ val builder = Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_media_play)
+ .setContentTitle("Title")
+ .setStyle(Notification.BigPictureStyle().bigLargeIcon(icon))
+ checkIconView(builder.createBigContentView()) { iconView ->
+ assertThat(iconView.visibility).isEqualTo(View.VISIBLE)
+ assertThat(iconView.width.toFloat())
+ .isWithin(1f)
+ .of((iconView.height * 4 / 3).toFloat())
+ assertThat(iconView.drawable.intrinsicWidth).isEqualTo(400)
+ assertThat(iconView.drawable.intrinsicHeight).isEqualTo(300)
+ }
}
@SmallTest
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index 6c349c7..307c3ce 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -913,7 +913,7 @@
final String contentDescription = "content description";
final Notification.BigPictureStyle bigPictureStyle = new Notification.BigPictureStyle()
- .bigPictureContentDescription(contentDescription);
+ .setContentDescription(contentDescription);
mNotification = new Notification.Builder(mContext, CHANNEL.getId())
.setStyle(bigPictureStyle)
diff --git a/tests/app/src/android/app/cts/ServiceTest.java b/tests/app/src/android/app/cts/ServiceTest.java
index a8c80a1..625163c 100644
--- a/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/app/src/android/app/cts/ServiceTest.java
@@ -1108,6 +1108,56 @@
assertNoNotification(2);
}
+ public void testForegroundService_notificationChannelDeletion() throws Exception {
+ NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
+
+ // Start service as foreground - it should show notification #1
+ mExpectedServiceState = STATE_START_1;
+ startForegroundService(COMMAND_START_FOREGROUND);
+ waitForResultOrThrow(DELAY, "service to start first time");
+ assertNotification(1, LocalForegroundService.getNotificationTitle(1));
+
+ try {
+ final String channel = LocalForegroundService.NOTIFICATION_CHANNEL_ID;
+ noMan.deleteNotificationChannel(channel);
+ fail("Deleting FGS notification channel did not throw");
+ } catch (SecurityException se) {
+ // Expected outcome, i.e. success case
+ } catch (Exception e) {
+ fail("Deleting FGS notification threw unexpected failure " + e);
+ }
+
+ mExpectedServiceState = STATE_DESTROY;
+ mContext.stopService(mLocalForegroundService);
+ waitForResultOrThrow(DELAY, "service to be destroyed");
+
+ }
+
+ public void testForegroundService_deferredNotificationChannelDeletion() throws Exception {
+ NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
+
+ // Start service as foreground - it should show notification #1
+ mExpectedServiceState = STATE_START_1;
+ startForegroundService(COMMAND_START_FOREGROUND_DEFER_NOTIFICATION);
+ waitForResultOrThrow(DELAY, "service to start first time");
+ assertNoNotification(1);
+
+ try {
+ final String channel = LocalForegroundService.NOTIFICATION_CHANNEL_ID;
+ noMan.deleteNotificationChannel(channel);
+ fail("Deleting FGS deferred notification channel did not throw");
+ } catch (SecurityException se) {
+ // Expected outcome
+ } catch (Exception e) {
+ fail("Deleting deferred FGS notification threw unexpected failure " + e);
+ }
+
+ mExpectedServiceState = STATE_DESTROY;
+ mContext.stopService(mLocalForegroundService);
+ waitForResultOrThrow(DELAY, "service to be destroyed");
+
+ }
+
public void testForegroundService_deferredNotification() throws Exception {
mExpectedServiceState = STATE_START_1;
startForegroundService(COMMAND_START_FOREGROUND_DEFER_NOTIFICATION);
diff --git a/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/app/src/android/app/cts/SystemFeaturesTest.java
index 03012c8..19dc7a3 100644
--- a/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -644,7 +644,7 @@
}
private boolean isAndroidEmulator() {
- return PropertyUtil.propertyEquals("ro.kernel.qemu", "1");
+ return PropertyUtil.propertyEquals("ro.boot.qemu", "1");
}
private void assertFeature(boolean exist, String feature) {
diff --git a/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
index 66afe72..e9e68b1 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/GlobalSearchSessionPlatformCtsTest.java
@@ -23,8 +23,14 @@
import android.app.appsearch.AppSearchManager;
import android.app.appsearch.AppSearchSessionShim;
import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GlobalSearchSessionShim;
import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.ReportSystemUsageRequest;
+import android.app.appsearch.ReportUsageRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResultsShim;
+import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
import android.content.ComponentName;
import android.content.Context;
@@ -38,6 +44,7 @@
import com.android.cts.appsearch.ICommandReceiver;
import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
+import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
import com.google.common.io.BaseEncoding;
@@ -111,11 +118,8 @@
@Before
public void setUp() throws Exception {
mContext = ApplicationProvider.getApplicationContext();
-
- mDb =
- AppSearchSessionShimImpl.createSearchSession(
- new AppSearchManager.SearchContext.Builder(DB_NAME).build())
- .get();
+ mDb = AppSearchSessionShimImpl.createSearchSession(
+ new AppSearchManager.SearchContext.Builder(DB_NAME).build()).get();
cleanup();
}
@@ -284,6 +288,102 @@
assertPackageCannotAccess(PKG_A);
}
+ @Test
+ public void testReportSystemUsage() throws Exception {
+ // Insert schema
+ mDb.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Insert two docs
+ GenericDocument document1 = new GenericDocument.Builder<>(
+ "namespace", "uri1", AppSearchEmail.SCHEMA_TYPE).build();
+ GenericDocument document2 = new GenericDocument.Builder<>(
+ "namespace", "uri2", AppSearchEmail.SCHEMA_TYPE).build();
+ mDb.put(new PutDocumentsRequest.Builder()
+ .addGenericDocuments(document1, document2).build()).get();
+
+ // Report some usages. uri1 has 2 app and 1 system usage, uri2 has 1 app and 2 system usage.
+ try (GlobalSearchSessionShim globalSearchSession
+ = GlobalSearchSessionShimImpl.createGlobalSearchSession().get()) {
+ mDb.reportUsage(new ReportUsageRequest.Builder("namespace")
+ .setUri("uri1")
+ .setUsageTimeMillis(10)
+ .build()).get();
+ mDb.reportUsage(new ReportUsageRequest.Builder("namespace")
+ .setUri("uri1")
+ .setUsageTimeMillis(20)
+ .build()).get();
+ globalSearchSession.reportSystemUsage(
+ new ReportSystemUsageRequest.Builder(
+ mContext.getPackageName(), DB_NAME, "namespace")
+ .setUri("uri1")
+ .setUsageTimeMillis(1000)
+ .build()).get();
+
+ mDb.reportUsage(new ReportUsageRequest.Builder("namespace")
+ .setUri("uri2")
+ .setUsageTimeMillis(100)
+ .build()).get();
+ globalSearchSession.reportSystemUsage(
+ new ReportSystemUsageRequest.Builder(
+ mContext.getPackageName(), DB_NAME, "namespace")
+ .setUri("uri2")
+ .setUsageTimeMillis(200)
+ .build()).get();
+ globalSearchSession.reportSystemUsage(
+ new ReportSystemUsageRequest.Builder(
+ mContext.getPackageName(), DB_NAME, "namespace")
+ .setUri("uri2")
+ .setUsageTimeMillis(150)
+ .build()).get();
+
+ // Sort by app usage count: uri1 should win
+ try (SearchResultsShim results = mDb.search("", new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_COUNT)
+ .build())) {
+ List<SearchResult> page = results.getNextPage().get();
+ assertThat(page).hasSize(2);
+ assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri1");
+ assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri2");
+ }
+
+ // Sort by app usage timestamp: uri2 should win
+ try (SearchResultsShim results = mDb.search("", new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP)
+ .build())) {
+ List<SearchResult> page = results.getNextPage().get();
+ assertThat(page).hasSize(2);
+ assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri2");
+ assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri1");
+ }
+
+ // Sort by system usage count: uri2 should win
+ try (SearchResultsShim results = mDb.search("", new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setRankingStrategy(SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_COUNT)
+ .build())) {
+ List<SearchResult> page = results.getNextPage().get();
+ assertThat(page).hasSize(2);
+ assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri2");
+ assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri1");
+ }
+
+ // Sort by system usage timestamp: uri1 should win
+ try (SearchResultsShim results = mDb.search("", new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setRankingStrategy(
+ SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP)
+ .build())) {
+ List<SearchResult> page = results.getNextPage().get();
+ assertThat(page).hasSize(2);
+ assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri1");
+ assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri2");
+ }
+ }
+ }
+
private void assertPackageCannotAccess(String pkg) throws Exception {
final GlobalSearchSessionPlatformCtsTest.TestServiceConnection serviceConnection =
bindToHelperService(pkg);
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java
index 3652809..53c43d8 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaCtsTest.java
@@ -79,7 +79,6 @@
public void testEquals_identical() {
AppSearchSchema schema1 =
new AppSearchSchema.Builder("Email")
- .setVersion(12345)
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
@@ -90,7 +89,6 @@
.build();
AppSearchSchema schema2 =
new AppSearchSchema.Builder("Email")
- .setVersion(12345)
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
@@ -157,14 +155,6 @@
}
@Test
- public void testEquals_failure_differentVersion() {
- AppSearchSchema schema1 = new AppSearchSchema.Builder("Email").setVersion(12345).build();
- AppSearchSchema schema2 = new AppSearchSchema.Builder("Email").setVersion(54321).build();
- assertThat(schema1).isNotEqualTo(schema2);
- assertThat(schema1.hashCode()).isNotEqualTo(schema2.hashCode());
- }
-
- @Test
public void testEquals_failure_differentOrder() {
AppSearchSchema schema1 =
new AppSearchSchema.Builder("Email")
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaMigrationCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaMigrationCtsTestBase.java
index ad20606..253595e 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaMigrationCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSchemaMigrationCtsTestBase.java
@@ -25,10 +25,10 @@
import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
-import android.app.appsearch.AppSearchMigrationHelper;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.AppSearchSessionShim;
import android.app.appsearch.GenericDocument;
+import android.app.appsearch.Migrator;
import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.SetSchemaRequest;
import android.app.appsearch.SetSchemaResponse;
@@ -39,19 +39,17 @@
import org.junit.Before;
import org.junit.Test;
-import java.util.HashSet;
-import java.util.Set;
import java.util.concurrent.ExecutionException;
/*
* For schema migration, we have 4 factors
* A. is ForceOverride set to true?
* B. is the schema change backwards compatible?
- * C. did any versions change?
+ * C. is shouldTrigger return true?
* D. is there a migration triggered for each incompatible type and no deleted types?
* If B is true then D could never be false, so that will give us 12 combinations.
*
- * Trigger Delete first second
+ * Trigger Delete First Second
* A B C D Migration Types SetSchema SetSchema
* TRUE TRUE TRUE TRUE Yes succeeds succeeds(noop)
* TRUE TRUE FALSE TRUE succeeds succeeds(noop)
@@ -63,21 +61,55 @@
* FALSE TRUE FALSE TRUE succeeds succeeds(noop)
* FALSE FALSE TRUE TRUE Yes fail succeeds
* FALSE FALSE TRUE FALSE Yes fail throw error
- * FALSE FALSE FALSE FALSE fail throw error
+ * FALSE FALSE FALSE TRUE Impossible case, migrators are inactivity
* FALSE FALSE FALSE FALSE fail throw error
*/
// TODO(b/178060626) add a platform version of this test
public abstract class AppSearchSchemaMigrationCtsTestBase {
private static final String DB_NAME = "";
- private static final AppSearchSchema.Migrator NO_OP_MIGRATOR =
- new AppSearchSchema.Migrator() {
+ private static final long DOCUMENT_CREATION_TIME = 12345L;
+ private static final Migrator ACTIVE_NOOP_MIGRATOR =
+ new Migrator() {
@Override
- public void onUpgrade(
- int currentVersion,
- int targetVersion,
- @NonNull AppSearchMigrationHelper helper)
- throws Exception {}
+ public boolean shouldMigrate(int currentVersion, int finalVersion) {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onUpgrade(
+ int currentVersion, int finalVersion, @NonNull GenericDocument document) {
+ return document;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onDowngrade(
+ int currentVersion, int finalVersion, @NonNull GenericDocument document) {
+ return document;
+ }
+ };
+ private static final Migrator INACTIVE_MIGRATOR =
+ new Migrator() {
+ @Override
+ public boolean shouldMigrate(int currentVersion, int finalVersion) {
+ return false;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onUpgrade(
+ int currentVersion, int finalVersion, @NonNull GenericDocument document) {
+ return document;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onDowngrade(
+ int currentVersion, int finalVersion, @NonNull GenericDocument document) {
+ return document;
+ }
};
private AppSearchSessionShim mDb;
@@ -111,6 +143,19 @@
.setForceOverride(true)
.build())
.get();
+ GenericDocument doc =
+ new GenericDocument.Builder<>("namespace", "uri0", "testSchema")
+ .setPropertyString("subject", "testPut example1")
+ .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+ .build();
+ AppSearchBatchResult<String, Void> result =
+ checkIsBatchResultSuccess(
+ mDb.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(doc)
+ .build()));
+ assertThat(result.getSuccesses()).containsExactly("uri0", null);
+ assertThat(result.getFailures()).isEmpty();
}
@After
@@ -120,11 +165,10 @@
}
@Test
- public void testSetSchema_migration_A_B_C_D() throws Exception {
+ public void testSchemaMigration_A_B_C_D() throws Exception {
// create a backwards compatible schema and update the version
AppSearchSchema B_C_Schema =
new AppSearchSchema.Builder("testSchema")
- .setVersion(1) // upgrade version
.addProperty(
new AppSearchSchema.StringPropertyConfig.Builder("subject")
.setCardinality(
@@ -141,14 +185,15 @@
mDb.setSchema(
new SetSchemaRequest.Builder()
.addSchemas(B_C_Schema)
- .setMigrator("testSchema", NO_OP_MIGRATOR)
+ .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
.setForceOverride(true)
+ .setVersion(2) // upgrade version
.build())
.get();
}
@Test
- public void testSetSchema_migration_A_B_NC_D() throws Exception {
+ public void testSchemaMigration_A_B_NC_D() throws Exception {
// create a backwards compatible schema but don't update the version
AppSearchSchema B_NC_Schema =
new AppSearchSchema.Builder("testSchema")
@@ -168,80 +213,75 @@
mDb.setSchema(
new SetSchemaRequest.Builder()
.addSchemas(B_NC_Schema)
- .setMigrator("testSchema", NO_OP_MIGRATOR)
+ .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
.setForceOverride(true)
.build())
.get();
}
@Test
- public void testSetSchema_migration_A_NB_C_D() throws Exception {
+ public void testSchemaMigration_A_NB_C_D() throws Exception {
// create a backwards incompatible schema and update the version
- AppSearchSchema NB_C_Schema =
- new AppSearchSchema.Builder("testSchema")
- .setVersion(1) // upgrade version
- .build();
+ AppSearchSchema NB_C_Schema = new AppSearchSchema.Builder("testSchema").build();
mDb.setSchema(
new SetSchemaRequest.Builder()
.addSchemas(NB_C_Schema)
- .setMigrator("testSchema", NO_OP_MIGRATOR)
+ .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
.setForceOverride(true)
+ .setVersion(2) // upgrade version
.build())
.get();
}
@Test
- public void testSetSchema_migration_A_NB_C_ND() throws Exception {
+ public void testSchemaMigration_A_NB_C_ND() throws Exception {
// create a backwards incompatible schema and update the version
- AppSearchSchema NB_C_Schema =
- new AppSearchSchema.Builder("testSchema")
- .setVersion(1) // upgrade version
- .build();
+ AppSearchSchema NB_C_Schema = new AppSearchSchema.Builder("testSchema").build();
mDb.setSchema(
new SetSchemaRequest.Builder()
.addSchemas(NB_C_Schema)
- .setMigrator("nonexistSchema", NO_OP_MIGRATOR) // ND
+ .setMigrator("testSchema", INACTIVE_MIGRATOR) // ND
.setForceOverride(true)
+ .setVersion(2) // upgrade version
.build())
.get();
}
@Test
- public void testSetSchema_migration_A_NB_NC_D() throws Exception {
+ public void testSchemaMigration_A_NB_NC_D() throws Exception {
// create a backwards incompatible schema but don't update the version
AppSearchSchema NB_NC_Schema = new AppSearchSchema.Builder("testSchema").build();
mDb.setSchema(
new SetSchemaRequest.Builder()
.addSchemas(NB_NC_Schema)
- .setMigrator("testSchema", NO_OP_MIGRATOR)
+ .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
.setForceOverride(true)
.build())
.get();
}
@Test
- public void testSetSchema_migration_A_NB_NC_ND() throws Exception {
+ public void testSchemaMigration_A_NB_NC_ND() throws Exception {
// create a backwards incompatible schema but don't update the version
AppSearchSchema $B_$C_Schema = new AppSearchSchema.Builder("testSchema").build();
mDb.setSchema(
new SetSchemaRequest.Builder()
.addSchemas($B_$C_Schema)
- .setMigrator("nonexistSchema", NO_OP_MIGRATOR) // ND
+ .setMigrator("testSchema", INACTIVE_MIGRATOR) // ND
.setForceOverride(true)
.build())
.get();
}
@Test
- public void testSetSchema_migration_NA_B_C_D() throws Exception {
+ public void testSchemaMigration_NA_B_C_D() throws Exception {
// create a backwards compatible schema and update the version
AppSearchSchema B_C_Schema =
new AppSearchSchema.Builder("testSchema")
- .setVersion(1) // upgrade version
.addProperty(
new AppSearchSchema.StringPropertyConfig.Builder("subject")
.setCardinality(
@@ -258,13 +298,14 @@
mDb.setSchema(
new SetSchemaRequest.Builder()
.addSchemas(B_C_Schema)
- .setMigrator("testSchema", NO_OP_MIGRATOR)
+ .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
+ .setVersion(2) // upgrade version
.build())
.get();
}
@Test
- public void testSetSchema_migration_NA_B_NC_D() throws Exception {
+ public void testSchemaMigration_NA_B_NC_D() throws Exception {
// create a backwards compatible schema but don't update the version
AppSearchSchema B_NC_Schema =
new AppSearchSchema.Builder("testSchema")
@@ -284,35 +325,30 @@
mDb.setSchema(
new SetSchemaRequest.Builder()
.addSchemas(B_NC_Schema)
- .setMigrator("testSchema", NO_OP_MIGRATOR)
+ .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
.setForceOverride(true)
.build())
.get();
}
@Test
- public void testSetSchema_migration_NA_NB_C_D() throws Exception {
+ public void testSchemaMigration_NA_NB_C_D() throws Exception {
// create a backwards incompatible schema and update the version
- AppSearchSchema NB_C_Schema =
- new AppSearchSchema.Builder("testSchema")
- .setVersion(1) // upgrade version
- .build();
+ AppSearchSchema NB_C_Schema = new AppSearchSchema.Builder("testSchema").build();
mDb.setSchema(
new SetSchemaRequest.Builder()
.addSchemas(NB_C_Schema)
- .setMigrator("testSchema", NO_OP_MIGRATOR)
+ .setMigrator("testSchema", ACTIVE_NOOP_MIGRATOR)
+ .setVersion(2) // upgrade version
.build())
.get();
}
@Test
- public void testSetSchema_migration_NA_NB_C_ND() throws Exception {
+ public void testSchemaMigration_NA_NB_C_ND() throws Exception {
// create a backwards incompatible schema and update the version
- AppSearchSchema $B_C_Schema =
- new AppSearchSchema.Builder("testSchema")
- .setVersion(1) // upgrade version
- .build();
+ AppSearchSchema $B_C_Schema = new AppSearchSchema.Builder("testSchema").build();
ExecutionException exception =
expectThrows(
@@ -322,33 +358,16 @@
new SetSchemaRequest.Builder()
.addSchemas($B_C_Schema)
.setMigrator(
- "nonexistSchema",
- NO_OP_MIGRATOR) // ND
+ "testSchema",
+ INACTIVE_MIGRATOR) // ND
+ .setVersion(2) // upgrade version
.build())
.get());
assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
}
@Test
- public void testSetSchema_migration_NA_NB_NC_D() throws Exception {
- // create a backwards incompatible schema but don't update the version
- AppSearchSchema $B_$C_Schema = new AppSearchSchema.Builder("testSchema").build();
-
- ExecutionException exception =
- expectThrows(
- ExecutionException.class,
- () ->
- mDb.setSchema(
- new SetSchemaRequest.Builder()
- .addSchemas($B_$C_Schema)
- .setMigrator("testSchema", NO_OP_MIGRATOR)
- .build())
- .get());
- assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
- }
-
- @Test
- public void testSetSchema_migration_NA_NB_NC_ND() throws Exception {
+ public void testSchemaMigration_NA_NB_NC_ND() throws Exception {
// create a backwards incompatible schema but don't update the version
AppSearchSchema $B_$C_Schema = new AppSearchSchema.Builder("testSchema").build();
@@ -360,15 +379,15 @@
new SetSchemaRequest.Builder()
.addSchemas($B_$C_Schema)
.setMigrator(
- "nonexistSchema",
- NO_OP_MIGRATOR) // ND
+ "testSchema",
+ INACTIVE_MIGRATOR) // ND
.build())
.get());
assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
}
@Test
- public void testSetSchema_migrate() throws Exception {
+ public void testSchemaMigration() throws Exception {
AppSearchSchema schema =
new AppSearchSchema.Builder("testSchema")
.addProperty(
@@ -424,7 +443,6 @@
// create new schema type and upgrade the version number
AppSearchSchema newSchema =
new AppSearchSchema.Builder("testSchema")
- .setVersion(1) // upgrade version
.addProperty(
new AppSearchSchema.StringPropertyConfig.Builder("subject")
.setCardinality(
@@ -440,52 +458,61 @@
// set the new schema to AppSearch, the first document will be migrated successfully but the
// second one will be failed.
- AppSearchSchema.Migrator migrator =
- new AppSearchSchema.Migrator() {
+
+ Migrator migrator =
+ new Migrator() {
@Override
- public void onUpgrade(
+ public boolean shouldMigrate(int currentVersion, int finalVersion) {
+ return currentVersion != finalVersion;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onUpgrade(
int currentVersion,
- int targetVersion,
- @NonNull AppSearchMigrationHelper helper)
- throws Exception {
- helper.queryAndTransform(
- "testSchema",
- (currentVersion1, finalVersion1, document) -> {
- if (document.getUri().equals("uri2")) {
- return new GenericDocument.Builder<>(
- document.getNamespace(),
- document.getUri(),
- document.getSchemaType())
- .setPropertyString("subject", "testPut example2")
- .setPropertyString(
- "to",
- "Except to fail, property not in the"
- + " schema")
- .build();
- }
- return new GenericDocument.Builder<>(
- document.getNamespace(),
- document.getUri(),
- document.getSchemaType())
- .setPropertyString(
- "subject", "testPut example1 migrated")
- .setCreationTimestampMillis(12345L)
- .build();
- });
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ if (document.getUri().equals("uri2")) {
+ return new GenericDocument.Builder<>(
+ document.getNamespace(),
+ document.getUri(),
+ document.getSchemaType())
+ .setPropertyString("subject", "testPut example2")
+ .setPropertyString(
+ "to", "Expect to fail, property not in the schema")
+ .build();
+ }
+ return new GenericDocument.Builder<>(
+ document.getNamespace(),
+ document.getUri(),
+ document.getSchemaType())
+ .setPropertyString("subject", "testPut example1 migrated")
+ .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+ .build();
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onDowngrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ throw new IllegalStateException(
+ "Downgrade should not be triggered for this test");
}
};
+
SetSchemaResponse setSchemaResponse =
mDb.setSchema(
new SetSchemaRequest.Builder()
.addSchemas(newSchema)
.setMigrator("testSchema", migrator)
+ .setVersion(2) // upgrade version
.build())
.get();
// Check the schema has been saved
- Set<AppSearchSchema> actualSchema = new HashSet<>();
- actualSchema.add(newSchema);
- assertThat(actualSchema).isEqualTo(mDb.getSchema().get());
+ assertThat(mDb.getSchema().get()).containsExactly(newSchema);
assertThat(setSchemaResponse.getDeletedTypes()).isEmpty();
assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("testSchema");
@@ -495,7 +522,7 @@
GenericDocument expected =
new GenericDocument.Builder<>("namespace", "uri1", "testSchema")
.setPropertyString("subject", "testPut example1 migrated")
- .setCreationTimestampMillis(12345L)
+ .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
.build();
assertThat(doGet(mDb, "namespace", "uri1")).containsExactly(expected);
@@ -507,4 +534,782 @@
assertThat(migrationFailure.getSchemaType()).isEqualTo("testSchema");
assertThat(migrationFailure.getUri()).isEqualTo("uri2");
}
+
+ @Test
+ public void testSchemaMigration_downgrade() throws Exception {
+ AppSearchSchema schema =
+ new AppSearchSchema.Builder("testSchema")
+ .addProperty(
+ new AppSearchSchema.StringPropertyConfig.Builder("subject")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(
+ AppSearchSchema.StringPropertyConfig
+ .INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ AppSearchSchema.StringPropertyConfig
+ .TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(
+ new AppSearchSchema.StringPropertyConfig.Builder("To")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(
+ AppSearchSchema.StringPropertyConfig
+ .INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ AppSearchSchema.StringPropertyConfig
+ .TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(schema)
+ .setForceOverride(true)
+ .setVersion(3)
+ .build())
+ .get();
+
+ GenericDocument doc1 =
+ new GenericDocument.Builder<>("namespace", "uri1", "testSchema")
+ .setPropertyString("subject", "testPut example1")
+ .setPropertyString("To", "testTo example1")
+ .build();
+
+ AppSearchBatchResult<String, Void> result =
+ checkIsBatchResultSuccess(
+ mDb.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(doc1)
+ .build()));
+ assertThat(result.getSuccesses()).containsExactly("uri1", null);
+ assertThat(result.getFailures()).isEmpty();
+
+ // create new schema type and upgrade the version number
+ AppSearchSchema newSchema =
+ new AppSearchSchema.Builder("testSchema")
+ .addProperty(
+ new AppSearchSchema.StringPropertyConfig.Builder("subject")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(
+ AppSearchSchema.StringPropertyConfig
+ .INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ AppSearchSchema.StringPropertyConfig
+ .TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+
+ // set the new schema to AppSearch
+ Migrator migrator =
+ new Migrator() {
+ @Override
+ public boolean shouldMigrate(int currentVersion, int finalVersion) {
+ return currentVersion != finalVersion;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onUpgrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ throw new IllegalStateException(
+ "Upgrade should not be triggered for this test");
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onDowngrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ return new GenericDocument.Builder<>(
+ document.getNamespace(),
+ document.getUri(),
+ document.getSchemaType())
+ .setPropertyString("subject", "testPut example1 migrated")
+ .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+ .build();
+ }
+ };
+
+ SetSchemaResponse setSchemaResponse =
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(newSchema)
+ .setMigrator("testSchema", migrator)
+ .setVersion(1) // downgrade version
+ .build())
+ .get();
+
+ // Check the schema has been saved
+ assertThat(mDb.getSchema().get().getSchemas()).containsExactly(newSchema);
+
+ assertThat(setSchemaResponse.getDeletedTypes()).isEmpty();
+ assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("testSchema");
+ assertThat(setSchemaResponse.getMigratedTypes()).containsExactly("testSchema");
+
+ // Check migrate is success
+ GenericDocument expected =
+ new GenericDocument.Builder<>("namespace", "uri1", "testSchema")
+ .setPropertyString("subject", "testPut example1 migrated")
+ .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+ .build();
+ assertThat(doGet(mDb, "namespace", "uri1")).containsExactly(expected);
+ }
+
+ @Test
+ public void testSchemaMigration_sameVersion() throws Exception {
+ AppSearchSchema schema =
+ new AppSearchSchema.Builder("testSchema")
+ .addProperty(
+ new AppSearchSchema.StringPropertyConfig.Builder("subject")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(
+ AppSearchSchema.StringPropertyConfig
+ .INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ AppSearchSchema.StringPropertyConfig
+ .TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(
+ new AppSearchSchema.StringPropertyConfig.Builder("To")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(
+ AppSearchSchema.StringPropertyConfig
+ .INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ AppSearchSchema.StringPropertyConfig
+ .TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(schema)
+ .setForceOverride(true)
+ .setVersion(3)
+ .build())
+ .get();
+
+ GenericDocument doc1 =
+ new GenericDocument.Builder<>("namespace", "uri1", "testSchema")
+ .setPropertyString("subject", "testPut example1")
+ .setPropertyString("To", "testTo example1")
+ .build();
+
+ AppSearchBatchResult<String, Void> result =
+ checkIsBatchResultSuccess(
+ mDb.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(doc1)
+ .build()));
+ assertThat(result.getSuccesses()).containsExactly("uri1", null);
+ assertThat(result.getFailures()).isEmpty();
+
+ // create new schema type with the same version number
+ AppSearchSchema newSchema =
+ new AppSearchSchema.Builder("testSchema")
+ .addProperty(
+ new AppSearchSchema.StringPropertyConfig.Builder("subject")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(
+ AppSearchSchema.StringPropertyConfig
+ .INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ AppSearchSchema.StringPropertyConfig
+ .TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+
+ // set the new schema to AppSearch
+ Migrator migrator =
+ new Migrator() {
+
+ @Override
+ public boolean shouldMigrate(int currentVersion, int finalVersion) {
+ return currentVersion != finalVersion;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onUpgrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ throw new IllegalStateException(
+ "Upgrade should not be triggered for this test");
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onDowngrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ throw new IllegalStateException(
+ "Downgrade should not be triggered for this test");
+ }
+ };
+
+ // SetSchema with forceOverride=false
+ ExecutionException exception =
+ expectThrows(
+ ExecutionException.class,
+ () ->
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(newSchema)
+ .setMigrator("testSchema", migrator)
+ .setVersion(3) // same version
+ .build())
+ .get());
+ assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
+
+ // SetSchema with forceOverride=true
+ SetSchemaResponse setSchemaResponse =
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(newSchema)
+ .setMigrator("testSchema", migrator)
+ .setVersion(3) // same version
+ .setForceOverride(true)
+ .build())
+ .get();
+
+ assertThat(mDb.getSchema().get().getSchemas()).containsExactly(newSchema);
+
+ assertThat(setSchemaResponse.getDeletedTypes()).isEmpty();
+ assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("testSchema");
+ assertThat(setSchemaResponse.getMigratedTypes()).isEmpty();
+ }
+
+ @Test
+ public void testSchemaMigration_noMigration() throws Exception {
+ AppSearchSchema schema =
+ new AppSearchSchema.Builder("testSchema")
+ .addProperty(
+ new AppSearchSchema.StringPropertyConfig.Builder("subject")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(
+ AppSearchSchema.StringPropertyConfig
+ .INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ AppSearchSchema.StringPropertyConfig
+ .TOKENIZER_TYPE_PLAIN)
+ .build())
+ .addProperty(
+ new AppSearchSchema.StringPropertyConfig.Builder("To")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(
+ AppSearchSchema.StringPropertyConfig
+ .INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ AppSearchSchema.StringPropertyConfig
+ .TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(schema)
+ .setForceOverride(true)
+ .setVersion(2)
+ .build())
+ .get();
+
+ GenericDocument doc1 =
+ new GenericDocument.Builder<>("namespace", "uri1", "testSchema")
+ .setPropertyString("subject", "testPut example1")
+ .setPropertyString("To", "testTo example1")
+ .build();
+
+ AppSearchBatchResult<String, Void> result =
+ checkIsBatchResultSuccess(
+ mDb.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(doc1)
+ .build()));
+ assertThat(result.getSuccesses()).containsExactly("uri1", null);
+ assertThat(result.getFailures()).isEmpty();
+
+ // create new schema type and upgrade the version number
+ AppSearchSchema newSchema =
+ new AppSearchSchema.Builder("testSchema")
+ .addProperty(
+ new AppSearchSchema.StringPropertyConfig.Builder("subject")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(
+ AppSearchSchema.StringPropertyConfig
+ .INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ AppSearchSchema.StringPropertyConfig
+ .TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+
+ // Set start version to be 3 means we won't trigger migration for 2.
+ Migrator migrator =
+ new Migrator() {
+
+ @Override
+ public boolean shouldMigrate(int currentVersion, int finalVersion) {
+ return currentVersion > 2 && currentVersion != finalVersion;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onUpgrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ throw new IllegalStateException(
+ "Upgrade should not be triggered for this test");
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onDowngrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ throw new IllegalStateException(
+ "Downgrade should not be triggered for this test");
+ }
+ };
+
+ // SetSchema with forceOverride=false
+ ExecutionException exception =
+ expectThrows(
+ ExecutionException.class,
+ () ->
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(newSchema)
+ .setMigrator("testSchema", migrator)
+ .setVersion(4) // upgrade version
+ .build())
+ .get());
+ assertThat(exception).hasMessageThat().contains("Schema is incompatible.");
+ }
+
+ @Test
+ public void testSchemaMigration_sourceToNowhere() throws Exception {
+ // set the source schema to AppSearch
+ AppSearchSchema schema = new AppSearchSchema.Builder("sourceSchema").build();
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(schema)
+ .setForceOverride(true)
+ .build())
+ .get();
+
+ // save a doc to the source type
+ GenericDocument doc =
+ new GenericDocument.Builder<>("namespace", "uri1", "sourceSchema")
+ .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+ .build();
+ AppSearchBatchResult<String, Void> result =
+ checkIsBatchResultSuccess(
+ mDb.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(doc)
+ .build()));
+ assertThat(result.getSuccesses()).containsExactly("uri1", null);
+ assertThat(result.getFailures()).isEmpty();
+
+ Migrator migrator_sourceToNowhere =
+ new Migrator() {
+ @Override
+ public boolean shouldMigrate(int currentVersion, int finalVersion) {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onUpgrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ return new GenericDocument.Builder<>(
+ "zombieNamespace", "zombieUri", "nonExistSchema")
+ .build();
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onDowngrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ return document;
+ }
+ };
+
+ // SetSchema with forceOverride=false
+ // Source type exist, destination type doesn't exist.
+ ExecutionException exception =
+ expectThrows(
+ ExecutionException.class,
+ () ->
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .setMigrator(
+ "sourceSchema",
+ migrator_sourceToNowhere)
+ .setVersion(2)
+ .build()) // upgrade version
+ .get());
+ assertThat(exception)
+ .hasMessageThat()
+ .contains(
+ "Receive a migrated document with schema type: nonExistSchema. "
+ + "But the schema types doesn't exist in the request");
+
+ // SetSchema with forceOverride=true
+ // Source type exist, destination type doesn't exist.
+ exception =
+ expectThrows(
+ ExecutionException.class,
+ () ->
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .setMigrator(
+ "sourceSchema",
+ migrator_sourceToNowhere)
+ .setForceOverride(true)
+ .setVersion(2)
+ .build()) // upgrade version
+ .get());
+ assertThat(exception)
+ .hasMessageThat()
+ .contains(
+ "Receive a migrated document with schema type: nonExistSchema. "
+ + "But the schema types doesn't exist in the request");
+ }
+
+ @Test
+ public void testSchemaMigration_nowhereToDestination() throws Exception {
+ // set the destination schema to AppSearch
+ AppSearchSchema destinationSchema =
+ new AppSearchSchema.Builder("destinationSchema").build();
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(destinationSchema)
+ .setForceOverride(true)
+ .build())
+ .get();
+
+ Migrator migrator_nowhereToDestination =
+ new Migrator() {
+ @Override
+ public boolean shouldMigrate(int currentVersion, int finalVersion) {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onUpgrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ return document;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onDowngrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ return document;
+ }
+ };
+
+ // Source type doesn't exist, destination type exist. Since source type doesn't exist,
+ // no matter force override or not, the migrator won't be invoked
+ // SetSchema with forceOverride=false
+ SetSchemaResponse setSchemaResponse =
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(destinationSchema)
+ .setMigrator(
+ "nonExistSchema", migrator_nowhereToDestination)
+ .setVersion(2) // upgrade version
+ .build())
+ .get();
+ assertThat(setSchemaResponse.getMigratedTypes()).isEmpty();
+
+ // SetSchema with forceOverride=true
+ setSchemaResponse =
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(destinationSchema)
+ .setMigrator(
+ "nonExistSchema", migrator_nowhereToDestination)
+ .setVersion(2) // upgrade version
+ .setForceOverride(true)
+ .build())
+ .get();
+ assertThat(setSchemaResponse.getMigratedTypes()).isEmpty();
+ }
+
+ @Test
+ public void testSchemaMigration_nowhereToNowhere() throws Exception {
+ // set empty schema
+ mDb.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
+ Migrator migrator_nowhereToNowhere =
+ new Migrator() {
+ @Override
+ public boolean shouldMigrate(int currentVersion, int finalVersion) {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onUpgrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ return document;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onDowngrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ return document;
+ }
+ };
+
+ // Source type doesn't exist, destination type exist. Since source type doesn't exist,
+ // no matter force override or not, the migrator won't be invoked
+ // SetSchema with forceOverride=false
+ SetSchemaResponse setSchemaResponse =
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .setMigrator("nonExistSchema", migrator_nowhereToNowhere)
+ .setVersion(2) // upgrade version
+ .build())
+ .get();
+ assertThat(setSchemaResponse.getMigratedTypes()).isEmpty();
+
+ // SetSchema with forceOverride=true
+ setSchemaResponse =
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .setMigrator("nonExistSchema", migrator_nowhereToNowhere)
+ .setVersion(2) // upgrade version
+ .setForceOverride(true)
+ .build())
+ .get();
+ assertThat(setSchemaResponse.getMigratedTypes()).isEmpty();
+ }
+
+ @Test
+ public void testSchemaMigration_toAnotherType() throws Exception {
+ // set the source schema to AppSearch
+ AppSearchSchema sourceSchema = new AppSearchSchema.Builder("sourceSchema").build();
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(sourceSchema)
+ .setForceOverride(true)
+ .build())
+ .get();
+
+ // save a doc to the source type
+ GenericDocument doc =
+ new GenericDocument.Builder<>("namespace", "uri1", "sourceSchema").build();
+ AppSearchBatchResult<String, Void> result =
+ checkIsBatchResultSuccess(
+ mDb.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(doc)
+ .build()));
+ assertThat(result.getSuccesses()).containsExactly("uri1", null);
+ assertThat(result.getFailures()).isEmpty();
+
+ // create the destination type and migrator
+ AppSearchSchema destinationSchema =
+ new AppSearchSchema.Builder("destinationSchema").build();
+ Migrator migrator =
+ new Migrator() {
+ @Override
+ public boolean shouldMigrate(int currentVersion, int finalVersion) {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onUpgrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ return new GenericDocument.Builder<>(
+ "namespace", document.getUri(), "destinationSchema")
+ .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+ .build();
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onDowngrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ return document;
+ }
+ };
+
+ // SetSchema with forceOverride=false and increase overall version
+ SetSchemaResponse setSchemaResponse =
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(destinationSchema)
+ .setMigrator("sourceSchema", migrator)
+ .setForceOverride(false)
+ .setVersion(2) // upgrade version
+ .build())
+ .get();
+ assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("sourceSchema");
+ assertThat(setSchemaResponse.getIncompatibleTypes()).isEmpty();
+ assertThat(setSchemaResponse.getMigratedTypes()).containsExactly("sourceSchema");
+
+ // Check successfully migrate the doc to the destination type
+ GenericDocument expected =
+ new GenericDocument.Builder<>("namespace", "uri1", "destinationSchema")
+ .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+ .build();
+ assertThat(doGet(mDb, "namespace", "uri1")).containsExactly(expected);
+ }
+
+ @Test
+ public void testSchemaMigration_toMultipleDestinationType() throws Exception {
+ // set the source schema to AppSearch
+ AppSearchSchema sourceSchema =
+ new AppSearchSchema.Builder("Person")
+ .addProperty(
+ new AppSearchSchema.Int64PropertyConfig.Builder("Age")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .build())
+ .build();
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(sourceSchema)
+ .setForceOverride(true)
+ .build())
+ .get();
+
+ // save a child and an adult to the Person type
+ GenericDocument childDoc =
+ new GenericDocument.Builder<>("namespace", "Person1", "Person")
+ .setPropertyLong("Age", 6)
+ .build();
+ GenericDocument adultDoc =
+ new GenericDocument.Builder<>("namespace", "Person2", "Person")
+ .setPropertyLong("Age", 36)
+ .build();
+ AppSearchBatchResult<String, Void> result =
+ checkIsBatchResultSuccess(
+ mDb.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(childDoc, adultDoc)
+ .build()));
+ assertThat(result.getSuccesses()).containsExactly("Person1", null, "Person2", null);
+ assertThat(result.getFailures()).isEmpty();
+
+ // create the migrator
+ Migrator migrator =
+ new Migrator() {
+ @Override
+ public boolean shouldMigrate(int currentVersion, int finalVersion) {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onUpgrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ if (document.getPropertyLong("Age") < 21) {
+ return new GenericDocument.Builder<>("namespace", "child-uri", "Child")
+ .setPropertyLong("Age", document.getPropertyLong("Age"))
+ .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+ .build();
+ } else {
+ return new GenericDocument.Builder<>("namespace", "adult-uri", "Adult")
+ .setPropertyLong("Age", document.getPropertyLong("Age"))
+ .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+ .build();
+ }
+ }
+
+ @NonNull
+ @Override
+ public GenericDocument onDowngrade(
+ int currentVersion,
+ int finalVersion,
+ @NonNull GenericDocument document) {
+ return document;
+ }
+ };
+
+ // create adult and child schema
+ AppSearchSchema adultSchema =
+ new AppSearchSchema.Builder("Adult")
+ .addProperty(
+ new AppSearchSchema.Int64PropertyConfig.Builder("Age")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .build())
+ .build();
+ AppSearchSchema childSchema =
+ new AppSearchSchema.Builder("Child")
+ .addProperty(
+ new AppSearchSchema.Int64PropertyConfig.Builder("Age")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .build())
+ .build();
+
+ // SetSchema with forceOverride=false and increase overall version
+ SetSchemaResponse setSchemaResponse =
+ mDb.setSchema(
+ new SetSchemaRequest.Builder()
+ .addSchemas(adultSchema, childSchema)
+ .setMigrator("Person", migrator)
+ .setForceOverride(false)
+ .setVersion(2) // upgrade version
+ .build())
+ .get();
+ assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Person");
+ assertThat(setSchemaResponse.getIncompatibleTypes()).isEmpty();
+ assertThat(setSchemaResponse.getMigratedTypes()).containsExactly("Person");
+
+ // Check successfully migrate the child doc
+ GenericDocument expectedInChild =
+ new GenericDocument.Builder<>("namespace", "child-uri", "Child")
+ .setPropertyLong("Age", 6)
+ .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+ .build();
+ assertThat(doGet(mDb, "namespace", "child-uri")).containsExactly(expectedInChild);
+
+ // Check successfully migrate the adult doc
+ GenericDocument expectedInAdult =
+ new GenericDocument.Builder<>("namespace", "adult-uri", "Adult")
+ .setPropertyLong("Age", 36)
+ .setCreationTimestampMillis(DOCUMENT_CREATION_TIME)
+ .build();
+ assertThat(doGet(mDb, "namespace", "adult-uri")).containsExactly(expectedInAdult);
+ }
}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java
index 6fc876a..a002aae 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/AppSearchSessionCtsTestBase.java
@@ -21,6 +21,7 @@
import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
import static com.android.server.appsearch.testing.AppSearchTestUtils.doGet;
+import static com.android.server.appsearch.testing.AppSearchTestUtils.retrieveAllSearchResults;
import static com.google.common.truth.Truth.assertThat;
@@ -36,6 +37,7 @@
import android.app.appsearch.AppSearchSessionShim;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetByUriRequest;
+import android.app.appsearch.GetSchemaResponse;
import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.RemoveByUriRequest;
import android.app.appsearch.ReportUsageRequest;
@@ -43,6 +45,7 @@
import android.app.appsearch.SearchResultsShim;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.StorageInfo;
import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
@@ -161,10 +164,10 @@
}
@Test
+ @Ignore("TODO(b/177266929)")
public void testSetSchema_updateVersion() throws Exception {
- AppSearchSchema oldSchema =
+ AppSearchSchema schema =
new AppSearchSchema.Builder("Email")
- .setVersion(1)
.addProperty(
new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
@@ -181,34 +184,111 @@
.build())
.build();
- mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(oldSchema).build()).get();
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(schema).setVersion(1).build())
+ .get();
- Set<AppSearchSchema> actualSchemaTypes = mDb1.getSchema().get();
- assertThat(actualSchemaTypes).containsExactly(oldSchema);
+ Set<AppSearchSchema> actualSchemaTypes = mDb1.getSchema().get().getSchemas();
+ assertThat(actualSchemaTypes).containsExactly(schema);
- AppSearchSchema newSchema =
- new AppSearchSchema.Builder("Email")
- .setVersion(2)
- .addProperty(
- new StringPropertyConfig.Builder("subject")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build())
- .addProperty(
- new StringPropertyConfig.Builder("body")
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(
- StringPropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build())
- .build();
+ // increase version number
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(schema).setVersion(2).build())
+ .get();
- mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(newSchema).build()).get();
+ GetSchemaResponse getSchemaResponse = mDb1.getSchema().get();
+ assertThat(getSchemaResponse.getSchemas()).containsExactly(schema);
+ assertThat(getSchemaResponse.getVersion()).isEqualTo(2);
+ }
- actualSchemaTypes = mDb1.getSchema().get();
- assertThat(actualSchemaTypes).containsExactly(newSchema);
+ @Test
+ public void testGetNamespaces() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+ .get();
+ assertThat(mDb1.getNamespaces().get()).isEmpty();
+
+ // Index a document
+ checkIsBatchResultSuccess(
+ mDb1.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(
+ new AppSearchEmail.Builder("namespace1", "uri1").build())
+ .build()));
+ assertThat(mDb1.getNamespaces().get()).containsExactly("namespace1");
+
+ // Index additional data
+ checkIsBatchResultSuccess(
+ mDb1.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(
+ new AppSearchEmail.Builder("namespace2", "uri1").build(),
+ new AppSearchEmail.Builder("namespace2", "uri2").build(),
+ new AppSearchEmail.Builder("namespace3", "uri1").build())
+ .build()));
+ assertThat(mDb1.getNamespaces().get())
+ .containsExactly("namespace1", "namespace2", "namespace3");
+
+ // Remove namespace2/uri2 -- namespace2 should still exist because of namespace2/uri1
+ checkIsBatchResultSuccess(
+ mDb1.remove(new RemoveByUriRequest.Builder("namespace2").addUris("uri2").build()));
+ assertThat(mDb1.getNamespaces().get())
+ .containsExactly("namespace1", "namespace2", "namespace3");
+
+ // Remove namespace2/uri1 -- namespace2 should now be gone
+ checkIsBatchResultSuccess(
+ mDb1.remove(new RemoveByUriRequest.Builder("namespace2").addUris("uri1").build()));
+ assertThat(mDb1.getNamespaces().get()).containsExactly("namespace1", "namespace3");
+
+ // Make sure the list of namespaces is preserved after restart
+ mDb1.close();
+ mDb1 = createSearchSession(DB_NAME_1).get();
+ assertThat(mDb1.getNamespaces().get()).containsExactly("namespace1", "namespace3");
+ }
+
+ @Test
+ public void testGetNamespaces_dbIsolation() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+ .get();
+ assertThat(mDb1.getNamespaces().get()).isEmpty();
+ assertThat(mDb2.getNamespaces().get()).isEmpty();
+
+ // Index documents
+ checkIsBatchResultSuccess(
+ mDb1.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(
+ new AppSearchEmail.Builder("namespace1_db1", "uri1")
+ .build())
+ .build()));
+ checkIsBatchResultSuccess(
+ mDb1.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(
+ new AppSearchEmail.Builder("namespace2_db1", "uri1")
+ .build())
+ .build()));
+ checkIsBatchResultSuccess(
+ mDb2.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(
+ new AppSearchEmail.Builder("namespace_db2", "uri1").build())
+ .build()));
+ assertThat(mDb1.getNamespaces().get()).containsExactly("namespace1_db1", "namespace2_db1");
+ assertThat(mDb2.getNamespaces().get()).containsExactly("namespace_db2");
+
+ // Make sure the list of namespaces is preserved after restart
+ mDb1.close();
+ mDb1 = createSearchSession(DB_NAME_1).get();
+ assertThat(mDb1.getNamespaces().get()).containsExactly("namespace1_db1", "namespace2_db1");
+ assertThat(mDb2.getNamespaces().get()).containsExactly("namespace_db2");
+ }
+
+ @Test
+ public void testGetSchema_emptyDB() throws Exception {
+ GetSchemaResponse getSchemaResponse = mDb1.getSchema().get();
+ assertThat(getSchemaResponse.getVersion()).isEqualTo(0);
}
@Test
@@ -944,11 +1024,16 @@
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setRankingStrategy(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE)
.build());
- List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+ List<SearchResult> results = retrieveAllSearchResults(searchResults);
// The email1 should be ranked higher because 'little' appears three times in email1 and
// only once in email2.
- assertThat(documents).containsExactly(email1, email2).inOrder();
+ assertThat(results).hasSize(2);
+ assertThat(results.get(0).getGenericDocument()).isEqualTo(email1);
+ assertThat(results.get(0).getRankingSignal())
+ .isGreaterThan(results.get(1).getRankingSignal());
+ assertThat(results.get(1).getGenericDocument()).isEqualTo(email2);
+ assertThat(results.get(1).getRankingSignal()).isGreaterThan(0);
// Query for "little OR stout". It should match both emails.
searchResults =
@@ -958,11 +1043,16 @@
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.setRankingStrategy(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE)
.build());
- documents = convertSearchResultsToDocuments(searchResults);
+ results = retrieveAllSearchResults(searchResults);
// The email2 should be ranked higher because 'little' appears once and "stout", which is a
// rarer term, appears once. email1 only has the three 'little' appearances.
- assertThat(documents).containsExactly(email2, email1).inOrder();
+ assertThat(results).hasSize(2);
+ assertThat(results.get(0).getGenericDocument()).isEqualTo(email2);
+ assertThat(results.get(0).getRankingSignal())
+ .isGreaterThan(results.get(1).getRankingSignal());
+ assertThat(results.get(1).getGenericDocument()).isEqualTo(email1);
+ assertThat(results.get(1).getRankingSignal()).isGreaterThan(0);
}
@Test
@@ -2562,7 +2652,7 @@
new SearchSpec.Builder()
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build()));
- assertThat(e).hasMessageThat().contains("AppSearchSession has already been closed");
+ assertThat(e).hasMessageThat().contains("SearchSession has already been closed");
} finally {
// To clean the data that has been added in the test, need to re-open the session and
// set an empty schema.
@@ -2591,47 +2681,52 @@
mDb1.reportUsage(
new ReportUsageRequest.Builder("namespace")
.setUri("uri1")
- .setUsageTimeMillis(10)
+ .setUsageTimeMillis(1000)
.build())
.get();
mDb1.reportUsage(
new ReportUsageRequest.Builder("namespace")
.setUri("uri1")
- .setUsageTimeMillis(20)
+ .setUsageTimeMillis(2000)
.build())
.get();
mDb1.reportUsage(
new ReportUsageRequest.Builder("namespace")
.setUri("uri1")
- .setUsageTimeMillis(30)
+ .setUsageTimeMillis(3000)
.build())
.get();
mDb1.reportUsage(
new ReportUsageRequest.Builder("namespace")
.setUri("uri2")
- .setUsageTimeMillis(100)
+ .setUsageTimeMillis(10000)
.build())
.get();
mDb1.reportUsage(
new ReportUsageRequest.Builder("namespace")
.setUri("uri2")
- .setUsageTimeMillis(200)
+ .setUsageTimeMillis(20000)
.build())
.get();
// Query by number of usages
- List<GenericDocument> documents =
- convertSearchResultsToDocuments(
+ List<SearchResult> results =
+ retrieveAllSearchResults(
mDb1.search(
"",
new SearchSpec.Builder()
.setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_COUNT)
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build()));
- assertThat(documents).containsExactly(email1, email2).inOrder();
+ // Email 1 has three usages and email 2 has two usages.
+ assertThat(results).hasSize(2);
+ assertThat(results.get(0).getGenericDocument()).isEqualTo(email1);
+ assertThat(results.get(0).getRankingSignal()).isEqualTo(3);
+ assertThat(results.get(1).getGenericDocument()).isEqualTo(email2);
+ assertThat(results.get(1).getRankingSignal()).isEqualTo(2);
- // Query by most recent usage
- documents =
+ // Query by most recent usag.
+ List<GenericDocument> documents =
convertSearchResultsToDocuments(
mDb1.search(
"",
@@ -2641,10 +2736,41 @@
.RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP)
.setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
.build()));
+ // TODO(b/182958600) Check the score for usage timestamp once b/182958600 is fixed.
assertThat(documents).containsExactly(email2, email1).inOrder();
}
@Test
+ @Ignore("TODO(b/182909475)")
+ public void testGetStorageInfo() throws Exception {
+ StorageInfo storageInfo = mDb1.getStorageInfo().get();
+ assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
+
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Still no storage space attributed with just a schema
+ storageInfo = mDb1.getStorageInfo().get();
+ assertThat(storageInfo.getSizeBytes()).isEqualTo(0);
+
+ // Index two documents.
+ AppSearchEmail email1 = new AppSearchEmail.Builder("namespace1", "uri1").build();
+ AppSearchEmail email2 = new AppSearchEmail.Builder("namespace1", "uri2").build();
+ AppSearchEmail email3 = new AppSearchEmail.Builder("namespace2", "uri1").build();
+ checkIsBatchResultSuccess(
+ mDb1.put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(email1, email2, email3)
+ .build()));
+
+ // Non-zero size now
+ storageInfo = mDb1.getStorageInfo().get();
+ assertThat(storageInfo.getSizeBytes()).isGreaterThan(0);
+ assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(3);
+ assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(2);
+ }
+
+ @Test
public void testFlush() throws Exception {
// Schema registration
mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
@@ -2671,4 +2797,90 @@
// The future returned from maybeFlush will be set as a void or an Exception on error.
mDb1.maybeFlush().get();
}
+
+ @Test
+ public void testQuery_ResultGroupingLimits() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index four documents.
+ AppSearchEmail inEmail1 =
+ new AppSearchEmail.Builder("namespace1", "uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail1).build()));
+ AppSearchEmail inEmail2 =
+ new AppSearchEmail.Builder("namespace1", "uri2")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail2).build()));
+ AppSearchEmail inEmail3 =
+ new AppSearchEmail.Builder("namespace2", "uri3")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail3).build()));
+ AppSearchEmail inEmail4 =
+ new AppSearchEmail.Builder("namespace2", "uri4")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail4).build()));
+
+ // Query with per package result grouping. Only the last document 'email4' should be
+ // returned.
+ SearchResultsShim searchResults =
+ mDb1.search(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setResultGrouping(
+ SearchSpec.GROUPING_TYPE_PER_PACKAGE, /*resultLimit=*/ 1)
+ .build());
+ List<GenericDocument> documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).containsExactly(inEmail4);
+
+ // Query with per namespace result grouping. Only the last document in each namespace should
+ // be returned ('email4' and 'email2').
+ searchResults =
+ mDb1.search(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setResultGrouping(
+ SearchSpec.GROUPING_TYPE_PER_NAMESPACE, /*resultLimit=*/ 1)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).containsExactly(inEmail4, inEmail2);
+
+ // Query with per package and per namespace result grouping. Only the last document in each
+ // namespace should be returned ('email4' and 'email2').
+ searchResults =
+ mDb1.search(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setResultGrouping(
+ SearchSpec.GROUPING_TYPE_PER_NAMESPACE
+ | SearchSpec.GROUPING_TYPE_PER_PACKAGE,
+ /*resultLimit=*/ 1)
+ .build());
+ documents = convertSearchResultsToDocuments(searchResults);
+ assertThat(documents).containsExactly(inEmail4, inEmail2);
+ }
}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java
index c00f65c..1fe5d76 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/GlobalSearchSessionCtsTestBase.java
@@ -21,18 +21,23 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.testng.Assert.expectThrows;
+
import android.annotation.NonNull;
import android.app.appsearch.AppSearchEmail;
+import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.AppSearchSchema.PropertyConfig;
import android.app.appsearch.AppSearchSessionShim;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GlobalSearchSessionShim;
import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.ReportSystemUsageRequest;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultsShim;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -42,11 +47,13 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.ExecutionException;
public abstract class GlobalSearchSessionCtsTestBase {
private AppSearchSessionShim mDb1;
@@ -618,4 +625,148 @@
.build();
assertThat(documents).containsExactly(expected1, expected2);
}
+
+ @Test
+ public void testQuery_ResultGroupingLimits() throws Exception {
+ // Schema registration
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+ .get();
+ mDb2.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+ .get();
+
+ // Index one document in 'namespace1' and one document in 'namespace2' into db1.
+ AppSearchEmail inEmail1 =
+ new AppSearchEmail.Builder("namespace1", "uri1")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail1).build()));
+ AppSearchEmail inEmail2 =
+ new AppSearchEmail.Builder("namespace2", "uri2")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail2).build()));
+
+ // Index one document in 'namespace1' and one document in 'namespace2' into db2.
+ AppSearchEmail inEmail3 =
+ new AppSearchEmail.Builder("namespace1", "uri3")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail3).build()));
+ AppSearchEmail inEmail4 =
+ new AppSearchEmail.Builder("namespace2", "uri4")
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(inEmail4).build()));
+
+ // Query with per package result grouping. Only the last document 'email4' should be
+ // returned.
+ List<GenericDocument> documents =
+ snapshotResults(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setResultGrouping(
+ SearchSpec.GROUPING_TYPE_PER_PACKAGE, /*resultLimit=*/ 1)
+ .build());
+ assertThat(documents).containsExactly(inEmail4);
+
+ // Query with per namespace result grouping. Only the last document in each namespace should
+ // be returned ('email4' and 'email3').
+ documents =
+ snapshotResults(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setResultGrouping(
+ SearchSpec.GROUPING_TYPE_PER_NAMESPACE, /*resultLimit=*/ 1)
+ .build());
+ assertThat(documents).containsExactly(inEmail4, inEmail3);
+
+ // Query with per package and per namespace result grouping. Only the last document in each
+ // namespace should be returned ('email4' and 'email3').
+ documents =
+ snapshotResults(
+ "body",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setResultGrouping(
+ SearchSpec.GROUPING_TYPE_PER_NAMESPACE
+ | SearchSpec.GROUPING_TYPE_PER_PACKAGE,
+ /*resultLimit=*/ 1)
+ .build());
+ assertThat(documents).containsExactly(inEmail4, inEmail3);
+ }
+
+ @Test
+ @Ignore("TODO(b/183031844)")
+ public void testReportSystemUsage_ForbiddenFromNonSystem() throws Exception {
+ // Index a document
+ mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+ .get();
+ AppSearchEmail email1 =
+ new AppSearchEmail.Builder("namespace", "uri1")
+ .setCreationTimestampMillis(1000)
+ .setFrom("from@example.com")
+ .setTo("to1@example.com", "to2@example.com")
+ .setSubject("testPut example")
+ .setBody("This is the body of the testPut email")
+ .build();
+ checkIsBatchResultSuccess(
+ mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+
+ // Query
+ List<SearchResult> page;
+ try (SearchResultsShim results =
+ mGlobalAppSearchManager.search(
+ "",
+ new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE)
+ .build())) {
+ page = results.getNextPage().get();
+ }
+ assertThat(page).isNotEmpty();
+ SearchResult firstResult = page.get(0);
+
+ ExecutionException exception =
+ expectThrows(
+ ExecutionException.class,
+ () ->
+ mGlobalAppSearchManager
+ .reportSystemUsage(
+ new ReportSystemUsageRequest.Builder(
+ firstResult.getPackageName(),
+ firstResult.getDatabaseName(),
+ firstResult
+ .getGenericDocument()
+ .getNamespace())
+ .setUri(
+ firstResult
+ .getGenericDocument()
+ .getUri())
+ .build())
+ .get());
+ assertThat(exception).hasCauseThat().isInstanceOf(AppSearchException.class);
+ AppSearchException ase = (AppSearchException) exception.getCause();
+ assertThat(ase.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
+ assertThat(ase)
+ .hasMessageThat()
+ .contains("com.android.cts.appsearch does not have access to report system usage");
+ }
}
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java
index 0ed7987..a3e1adc 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/SearchSpecCtsTest.java
@@ -48,6 +48,10 @@
.setResultCountPerPage(42)
.setOrder(SearchSpec.ORDER_ASCENDING)
.setRankingStrategy(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE)
+ .setResultGrouping(
+ SearchSpec.GROUPING_TYPE_PER_NAMESPACE
+ | SearchSpec.GROUPING_TYPE_PER_PACKAGE,
+ /*limit=*/ 37)
.build();
assertThat(searchSpec.getTermMatch()).isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
@@ -67,5 +71,10 @@
assertThat(searchSpec.getOrder()).isEqualTo(SearchSpec.ORDER_ASCENDING);
assertThat(searchSpec.getRankingStrategy())
.isEqualTo(SearchSpec.RANKING_STRATEGY_DOCUMENT_SCORE);
+ assertThat(searchSpec.getResultGroupingTypeFlags())
+ .isEqualTo(
+ SearchSpec.GROUPING_TYPE_PER_NAMESPACE
+ | SearchSpec.GROUPING_TYPE_PER_PACKAGE);
+ assertThat(searchSpec.getResultGroupingLimit()).isEqualTo(37);
}
}
diff --git a/tests/autofillservice/Android.bp b/tests/autofillservice/Android.bp
index 5e022ea..f973d0e 100644
--- a/tests/autofillservice/Android.bp
+++ b/tests/autofillservice/Android.bp
@@ -32,6 +32,7 @@
// TODO: remove once Android migrates to JUnit 4.12,
// which provides assertThrows
"testng",
+ "cts-wm-util",
],
srcs: ["src/**/*.java"],
// Tag this module as a cts test artifact
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
index 0cff3d2..20437f7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
@@ -34,12 +34,14 @@
import android.autofillservice.cts.testcore.AutofillActivityTestRule;
import android.autofillservice.cts.testcore.CannedFillResponse;
import android.autofillservice.cts.testcore.Helper;
-import android.content.Intent;
+import android.graphics.Rect;
import android.platform.test.annotations.AppModeFull;
+import android.server.wm.TestTaskOrganizer;
import android.view.View;
import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.rules.RuleChain;
@@ -52,7 +54,7 @@
extends AutoFillServiceTestCase.AutoActivityLaunch<MultiWindowLoginActivity> {
private LoginActivity mActivity;
- private ActivityTaskManager mAtm;
+ private TestTaskOrganizer mTaskOrganizer;
@Override
protected AutofillActivityTestRule<MultiWindowLoginActivity> getActivityRule() {
@@ -61,7 +63,7 @@
@Override
protected void afterActivityLaunched() {
mActivity = getActivity();
- mAtm = mContext.getSystemService(ActivityTaskManager.class);
+ mTaskOrganizer = new TestTaskOrganizer(mContext);
}
};
}
@@ -82,33 +84,32 @@
ActivityTaskManager.supportsSplitScreenMultiWindow(mActivity));
}
+ @After
+ public void tearDown() {
+ mTaskOrganizer.unregisterOrganizerIfNeeded();
+ }
+
/**
- * Touch a view and exepct autofill window change
+ * Touch a view and expect autofill window change
*/
protected void tapViewAndExpectWindowEvent(View view) throws TimeoutException {
mUiBot.waitForWindowChange(() -> tap(view));
}
- protected String runAmStartActivity(Class<? extends Activity> activityClass, int flags) {
- return runAmStartActivity(activityClass.getName(), flags);
- }
-
- protected String runAmStartActivity(String activity, int flags) {
- return runShellCommand("am start %s/%s -f 0x%s", mPackageName, activity,
- Integer.toHexString(flags));
- }
-
/**
- * Put activity in TOP, will be followed by amStartActivity()
+ * Touch specific position on device display and expect autofill window change.
*/
- protected void splitWindow(Activity activity) {
- mAtm.setTaskWindowingModeSplitScreenPrimary(activity.getTaskId(), true /* toTop */);
+ protected void tapPointAndExpectWindowEvent(int x, int y) {
+ mUiBot.waitForWindowChange(() -> runShellCommand("input touchscreen tap %d %d", x, y));
+ }
+
+ protected String runAmStartActivity(String activity) {
+ return runShellCommand("am start %s/%s", mPackageName, activity);
}
protected void amStartActivity(Class<? extends Activity> activity2) {
// it doesn't work using startActivity(intent), have to go through shell command.
- runAmStartActivity(activity2,
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
+ runAmStartActivity(activity2.getName());
}
@Test
@@ -130,12 +131,13 @@
MultiWindowLoginActivity.expectNewInstance(false);
MultiWindowEmptyActivity.expectNewInstance(true);
- splitWindow(mActivity);
+ mTaskOrganizer.putTaskInSplitPrimary(mActivity.getTaskId());
mUiBot.waitForIdleSync();
MultiWindowLoginActivity loginActivity = MultiWindowLoginActivity.waitNewInstance();
amStartActivity(MultiWindowEmptyActivity.class);
MultiWindowEmptyActivity emptyActivity = MultiWindowEmptyActivity.waitNewInstance();
+ mTaskOrganizer.putTaskInSplitSecondary(emptyActivity.getTaskId());
// Make sure both activities are showing
mUiBot.assertShownByRelativeId(Helper.ID_USERNAME); // MultiWindowLoginActivity
@@ -155,7 +157,10 @@
assertThat(emptyActivity.hasWindowFocus()).isFalse();
// Tap on EmptyActivity and fill ui is gone.
- tapViewAndExpectWindowEvent(emptyActivity.getEmptyView());
+ Rect emptyActivityBounds = mTaskOrganizer.getSecondaryTaskBounds();
+ // Because tap(View) will get wrong physical start position of view while in split screen
+ // and make bot cannot tap on emptyActivity, so use task bounds and tap its center.
+ tapPointAndExpectWindowEvent(emptyActivityBounds.centerX(), emptyActivityBounds.centerY());
mUiBot.assertNoDatasetsEver();
assertThat(emptyActivity.hasWindowFocus()).isTrue();
// LoginActivity username field is still focused but window has no focus
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
index 89bbbf5..45a593e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedLoginActivityTest.java
@@ -67,6 +67,8 @@
import android.view.autofill.AutofillValue;
import android.widget.EditText;
+import androidx.test.filters.FlakyTest;
+
import org.junit.Test;
import java.util.Set;
@@ -330,7 +332,7 @@
mAugmentedUiBot.assertUiGone();
}
- @Presubmit
+ @FlakyTest(bugId = 162372863) // Re-add @Presubmit after fixing.
@Test
public void testAutoFill_augmentedFillRequestCancelled() throws Exception {
// Set services
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
index 879aa8e..b206d5b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/AutoFillServiceTestCase.java
@@ -108,7 +108,7 @@
return false;
}
- protected static UiBot getInlineUiBot() {
+ protected static InlineUiBot getInlineUiBot() {
return sDefaultUiBot2;
}
@@ -480,7 +480,7 @@
}
protected static final UiBot sDefaultUiBot = new UiBot();
- protected static final UiBot sDefaultUiBot2 = new InlineUiBot();
+ protected static final InlineUiBot sDefaultUiBot2 = new InlineUiBot();
private AutoFillServiceTestCase() {
throw new UnsupportedOperationException("Contain static stuff only");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineTooltipTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineTooltipTest.java
new file mode 100644
index 0000000..79095cf
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineTooltipTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.inline;
+
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.getContext;
+import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceInlineEnabled.SERVICE_NAME;
+
+import android.autofillservice.cts.commontests.AbstractLoginActivityTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InlineUiBot;
+
+import org.junit.Test;
+import org.junit.rules.TestRule;
+
+/**
+ * Tests inline suggestions tooltip behaviors.
+ */
+public class InlineTooltipTest extends AbstractLoginActivityTestCase {
+
+ InlineUiBot mInlineUiBot;
+
+ public InlineTooltipTest() {
+ super(getInlineUiBot());
+ mInlineUiBot = getInlineUiBot();
+ }
+
+ @Override
+ protected boolean isInlineMode() {
+ return true;
+ }
+
+ @Override
+ public TestRule getMainTestRule() {
+ return InlineUiBot.annotateRule(super.getMainTestRule());
+ }
+
+ @Override
+ protected void enableService() {
+ Helper.enableAutofillService(getContext(), SERVICE_NAME);
+ }
+
+ @Test
+ public void testShowTooltip() throws Exception {
+ // Set service.
+ enableService();
+
+ final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setPresentation(createPresentation("The Username"))
+ .setInlinePresentation(createInlinePresentation("The Username"))
+ .setInlineTooltipPresentation(
+ Helper.createInlineTooltipPresentation("The Username Tooltip"))
+ .build());
+
+ sReplier.addResponse(builder.build());
+
+ // Trigger auto-fill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdleSync();
+ sReplier.getNextFillRequest();
+
+ mUiBot.assertDatasets("The Username");
+ mInlineUiBot.assertTooltipShowing("The Username Tooltip");
+ }
+
+ @Test
+ public void testShowTooltipWithTwoFields() throws Exception {
+ // Set service.
+ enableService();
+
+ final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setPresentation(createPresentation("The Username"))
+ .setInlinePresentation(createInlinePresentation("The Username"))
+ .setInlineTooltipPresentation(
+ Helper.createInlineTooltipPresentation("The Username Tooltip"))
+ .build())
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("The Password"))
+ .setInlinePresentation(createInlinePresentation("The Password"))
+ .build())
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_PASSWORD, "lollipop")
+ .setPresentation(createPresentation("The Password2"))
+ .setInlinePresentation(createInlinePresentation("The Password2"))
+ .setInlineTooltipPresentation(
+ Helper.createInlineTooltipPresentation("The Password Tooltip"))
+ .build());
+
+ sReplier.addResponse(builder.build());
+
+ // Trigger auto-fill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdleSync();
+ sReplier.getNextFillRequest();
+
+ mUiBot.assertDatasets("The Username");
+ mInlineUiBot.assertTooltipShowing("The Username Tooltip");
+
+ // Switch focus to password
+ mUiBot.selectByRelativeId(ID_PASSWORD);
+ mUiBot.waitForIdleSync();
+
+ mUiBot.assertDatasets("The Password", "The Password2");
+ mInlineUiBot.assertTooltipShowing("The Password Tooltip");
+
+ // Switch focus back to username
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdleSync();
+
+ mUiBot.assertDatasets("The Username");
+ mInlineUiBot.assertTooltipShowing("The Username Tooltip");
+
+ mActivity.expectAutoFill("dude");
+ mUiBot.selectDataset("The Username");
+ mUiBot.waitForIdleSync();
+
+ // Check the results.
+ mActivity.assertAutoFilled();
+ }
+
+ @Test
+ public void testShowTooltipWithSecondDataset() throws Exception {
+ // Set service.
+ enableService();
+
+ final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setPresentation(createPresentation("The Username"))
+ .setInlinePresentation(createInlinePresentation("The Username"))
+ .build())
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_USERNAME, "sweet")
+ .setPresentation(createPresentation("The Username2"))
+ .setInlinePresentation(createInlinePresentation("The Username2"))
+ .setInlineTooltipPresentation(
+ Helper.createInlineTooltipPresentation("The Username Tooltip"))
+ .build())
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_USERNAME, "candy")
+ .setPresentation(createPresentation("The Username3"))
+ .setInlinePresentation(createInlinePresentation("The Username3"))
+ .build());
+
+ sReplier.addResponse(builder.build());
+
+ // Trigger auto-fill.
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdleSync();
+ sReplier.getNextFillRequest();
+
+ mUiBot.assertDatasets("The Username", "The Username2", "The Username3");
+ mInlineUiBot.assertTooltipShowing("The Username Tooltip");
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
index c276a7c..3ec1f12 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
@@ -603,9 +603,11 @@
private final Map<String, AutofillValue> mFieldValues;
private final Map<String, RemoteViews> mFieldPresentations;
private final Map<String, InlinePresentation> mFieldInlinePresentations;
+ private final Map<String, InlinePresentation> mFieldInlineTooltipPresentations;
private final Map<String, Pair<Boolean, Pattern>> mFieldFilters;
private final RemoteViews mPresentation;
private final InlinePresentation mInlinePresentation;
+ private final InlinePresentation mInlineTooltipPresentation;
private final IntentSender mAuthentication;
private final String mId;
@@ -613,9 +615,11 @@
mFieldValues = builder.mFieldValues;
mFieldPresentations = builder.mFieldPresentations;
mFieldInlinePresentations = builder.mFieldInlinePresentations;
+ mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations;
mFieldFilters = builder.mFieldFilters;
mPresentation = builder.mPresentation;
mInlinePresentation = builder.mInlinePresentation;
+ mInlineTooltipPresentation = builder.mInlineTooltipPresentation;
mAuthentication = builder.mAuthentication;
mId = builder.mId;
}
@@ -639,12 +643,15 @@
public Dataset asDatasetWithAutofillIdResolver(
Function<String, AutofillId> autofillIdResolver) {
final Dataset.Builder builder = mPresentation != null
- ? mInlinePresentation == null
? new Dataset.Builder(mPresentation)
- : new Dataset.Builder(mPresentation).setInlinePresentation(mInlinePresentation)
- : mInlinePresentation == null
- ? new Dataset.Builder()
- : new Dataset.Builder(mInlinePresentation);
+ : new Dataset.Builder();
+ if (mInlinePresentation != null) {
+ if (mInlineTooltipPresentation != null) {
+ builder.setInlinePresentation(mInlinePresentation, mInlineTooltipPresentation);
+ } else {
+ builder.setInlinePresentation(mInlinePresentation);
+ }
+ }
if (mFieldValues != null) {
for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) {
@@ -657,27 +664,43 @@
final AutofillValue value = entry.getValue();
final RemoteViews presentation = mFieldPresentations.get(id);
final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(id);
+ final InlinePresentation tooltipPresentation =
+ mFieldInlineTooltipPresentations.get(id);
final Pair<Boolean, Pattern> filter = mFieldFilters.get(id);
if (presentation != null) {
if (filter == null) {
if (inlinePresentation != null) {
- builder.setValue(autofillId, value, presentation,
- inlinePresentation);
+ if (tooltipPresentation != null) {
+ builder.setValue(autofillId, value, presentation,
+ inlinePresentation, tooltipPresentation);
+ } else {
+ builder.setValue(autofillId, value, presentation,
+ inlinePresentation);
+ }
} else {
builder.setValue(autofillId, value, presentation);
}
} else {
if (inlinePresentation != null) {
- builder.setValue(autofillId, value, filter.second, presentation,
- inlinePresentation);
+ if (tooltipPresentation != null) {
+ builder.setValue(autofillId, value, filter.second, presentation,
+ inlinePresentation, tooltipPresentation);
+ } else {
+ builder.setValue(autofillId, value, filter.second, presentation,
+ inlinePresentation);
+ }
} else {
builder.setValue(autofillId, value, filter.second, presentation);
}
}
} else {
if (inlinePresentation != null) {
- builder.setFieldInlinePresentation(autofillId, value,
- filter != null ? filter.second : null, inlinePresentation);
+ if (tooltipPresentation != null) {
+ throw new IllegalStateException("presentation can not be null");
+ } else {
+ builder.setFieldInlinePresentation(autofillId, value,
+ filter != null ? filter.second : null, inlinePresentation);
+ }
} else {
if (filter == null) {
builder.setValue(autofillId, value);
@@ -698,6 +721,7 @@
+ ", hasInlinePresentation=" + (mInlinePresentation != null)
+ ", fieldPresentations=" + (mFieldPresentations)
+ ", fieldInlinePresentations=" + (mFieldInlinePresentations)
+ + ", fieldTooltipInlinePresentations=" + (mFieldInlineTooltipPresentations)
+ ", hasAuthentication=" + (mAuthentication != null)
+ ", fieldValues=" + mFieldValues
+ ", fieldFilters=" + mFieldFilters + "]";
@@ -708,12 +732,15 @@
private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
private final Map<String, InlinePresentation> mFieldInlinePresentations =
new HashMap<>();
+ private final Map<String, InlinePresentation> mFieldInlineTooltipPresentations =
+ new HashMap<>();
private final Map<String, Pair<Boolean, Pattern>> mFieldFilters = new HashMap<>();
private RemoteViews mPresentation;
private InlinePresentation mInlinePresentation;
private IntentSender mAuthentication;
private String mId;
+ private InlinePresentation mInlineTooltipPresentation;
public Builder() {
@@ -858,6 +885,21 @@
* {@link IdMode}.
*/
public Builder setField(String id, String text, RemoteViews presentation,
+ InlinePresentation inlinePresentation,
+ InlinePresentation inlineTooltipPresentation) {
+ setField(id, text, presentation, inlinePresentation);
+ mFieldInlineTooltipPresentations.put(id, inlineTooltipPresentation);
+ return this;
+ }
+
+ /**
+ * Sets the canned value of a field based on its {@code id}.
+ *
+ * <p>The meaning of the id is defined by the object using the canned dataset.
+ * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+ * {@link IdMode}.
+ */
+ public Builder setField(String id, String text, RemoteViews presentation,
InlinePresentation inlinePresentation, Pattern filter) {
setField(id, text, presentation, inlinePresentation);
mFieldFilters.put(id, new Pair<>(true, filter));
@@ -865,6 +907,23 @@
}
/**
+ * Sets the canned value of a field based on its {@code id}.
+ *
+ * <p>The meaning of the id is defined by the object using the canned dataset.
+ * For example, {@link InstrumentedAutoFillService.Replier} resolves the id based on
+ * {@link IdMode}.
+ */
+ public Builder setField(String id, String text, RemoteViews presentation,
+ InlinePresentation inlinePresentation,
+ InlinePresentation inlineTooltipPresentation,
+ Pattern filter) {
+ setField(id, text, presentation, inlinePresentation, inlineTooltipPresentation);
+ mFieldFilters.put(id, new Pair<>(true, filter));
+
+ return this;
+ }
+
+ /**
* Sets the view to present the response in the UI.
*/
public Builder setPresentation(RemoteViews presentation) {
@@ -880,6 +939,14 @@
return this;
}
+ /**
+ * Sets the inline tooltip to present the response in the UI.
+ */
+ public Builder setInlineTooltipPresentation(InlinePresentation tooltip) {
+ mInlineTooltipPresentation = tooltip;
+ return this;
+ }
+
public Builder setPresentation(String message, boolean inlineMode) {
mPresentation = createPresentation(message);
if (inlineMode) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
index a3097c8..46a613f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
@@ -1549,6 +1549,22 @@
.build(), /* pinned= */ pinned);
}
+ public static InlinePresentation createInlineTooltipPresentation(
+ @NonNull String message) {
+ final PendingIntent dummyIntent = PendingIntent.getActivity(getContext(), 0, new Intent(),
+ PendingIntent.FLAG_IMMUTABLE);
+ return createInlineTooltipPresentation(message, dummyIntent);
+ }
+
+ private static InlinePresentation createInlineTooltipPresentation(
+ @NonNull String message, @NonNull PendingIntent attribution) {
+ return InlinePresentation.createTooltipPresentation(
+ InlineSuggestionUi.newContentBuilder(attribution)
+ .setTitle(message).build().getSlice(),
+ new InlinePresentationSpec.Builder(new Size(100, 100), new Size(400, 100))
+ .build());
+ }
+
public static void mockSwitchInputMethod(@NonNull Context context) throws Exception {
final ContentResolver cr = context.getContentResolver();
final int subtype = Settings.Secure.getInt(cr, SELECTED_INPUT_METHOD_SUBTYPE);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java
index 88f97c2..50892fc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/InlineUiBot.java
@@ -126,6 +126,13 @@
strip.fling(direction, speed);
}
+ public void assertTooltipShowing(String text) throws Exception {
+ final UiObject2 strip = waitForObject(By.text(text), UI_TIMEOUT);
+ if (strip == null) {
+ throw new AssertionError("not find inline tooltip by text: " + text);
+ }
+ }
+
private UiObject2 findSuggestionStrip(Timeout timeout) throws Exception {
return waitForObject(SUGGESTION_STRIP_SELECTOR, timeout);
}
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiResolutionImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiResolutionImageReaderTest.java
index 5d4fe03..32c0a33 100644
--- a/tests/camera/src/android/hardware/camera2/cts/MultiResolutionImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/MultiResolutionImageReaderTest.java
@@ -251,7 +251,7 @@
int numFrameVerified = repeating ? NUM_FRAME_VERIFIED : 1;
// Create multi-resolution ImageReader
- mMultiResolutionImageReader = MultiResolutionImageReader.newInstance(
+ mMultiResolutionImageReader = new MultiResolutionImageReader(
multiResolutionStreams, format, MAX_NUM_IMAGES);
mListener = new SimpleMultiResolutionImageReaderListener(
mMultiResolutionImageReader, MAX_NUM_IMAGES, repeating);
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiResolutionReprocessCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiResolutionReprocessCaptureTest.java
index 10c1926..95d5661 100644
--- a/tests/camera/src/android/hardware/camera2/cts/MultiResolutionReprocessCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/MultiResolutionReprocessCaptureTest.java
@@ -342,7 +342,7 @@
}
// create an MultiResolutionImageReader for the regular capture
- mMultiResImageReader = MultiResolutionImageReader.newInstance(inputInfo,
+ mMultiResImageReader = new MultiResolutionImageReader(inputInfo,
inputFormat, maxImages);
mMultiResImageReaderListener = new SimpleMultiResolutionImageReaderListener(
mMultiResImageReader, 1, /*repeating*/false);
@@ -351,7 +351,7 @@
if (!mShareOneReader) {
// create an MultiResolutionImageReader for the reprocess capture
- mSecondMultiResImageReader = MultiResolutionImageReader.newInstance(
+ mSecondMultiResImageReader = new MultiResolutionImageReader(
outputInfo, outputFormat, maxImages);
mSecondMultiResImageReaderListener = new SimpleMultiResolutionImageReaderListener(
mSecondMultiResImageReader, maxImages, /*repeating*/ false);
diff --git a/tests/contentcaptureservice/Android.bp b/tests/contentcaptureservice/Android.bp
index 94ce748..f6d6ed4 100644
--- a/tests/contentcaptureservice/Android.bp
+++ b/tests/contentcaptureservice/Android.bp
@@ -28,9 +28,11 @@
// TODO: remove once Android migrates to JUnit 4.12,
// which provides assertThrows
"testng",
- "OutOfPackageDataSharingServiceAidl",
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.aidl",
+ ],
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp b/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp
index cf53a7a..fae0054 100644
--- a/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp
+++ b/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp
@@ -24,7 +24,6 @@
sdk_version: "current",
static_libs: [
"androidx.annotation_annotation",
- "OutOfPackageDataSharingServiceAidl",
],
srcs: ["src/**/*.java"],
// Tag this module as a cts test artifact
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml b/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
index 239b410..164cdcf 100644
--- a/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
+++ b/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
@@ -35,17 +35,6 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
-
- <receiver
- android:name=".SelfDestructReceiver"
- android:exported="true" />
-
- <service
- android:name=".OutOfPackageDataSharingService"
- android:exported="true"
- android:label="OutOfPackageDataSharingService" />
-
- <uses-library android:name="android.test.runner" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/OutOfPackageDataSharingService.java b/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/OutOfPackageDataSharingService.java
deleted file mode 100644
index 7fb81c8..0000000
--- a/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/OutOfPackageDataSharingService.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR 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.Service;
-import android.content.Intent;
-import android.os.IBinder;
-import android.view.contentcapture.ContentCaptureManager;
-import android.contentcaptureservice.cts.IOutOfPackageDataSharingService;
-
-import androidx.annotation.Nullable;
-
-public class OutOfPackageDataSharingService extends Service {
-
- private final IBinder mBinder = new IOutOfPackageDataSharingService.Stub() {
- @Override
- public boolean isContentCaptureManagerAvailable() {
- ContentCaptureManager manager =
- getApplicationContext().getSystemService(ContentCaptureManager.class);
- return manager != null && manager.isContentCaptureEnabled();
- }
- };
-
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-}
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/SelfDestructReceiver.java b/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/SelfDestructReceiver.java
deleted file mode 100644
index 470a5f6..0000000
--- a/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/SelfDestructReceiver.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR 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.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Process;
-import android.util.Log;
-
-/**
- * A {@link BroadcastReceiver} that kills its process.
- */
-public class SelfDestructReceiver extends BroadcastReceiver {
-
- private static final String TAG = SelfDestructReceiver.class.getSimpleName();
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.i(TAG, "Goodbye, cruel world!");
- Process.killProcess(Process.myPid());
- }
-}
diff --git a/tests/contentcaptureservice/TEST_MAPPING b/tests/contentcaptureservice/TEST_MAPPING
new file mode 100644
index 0000000..32803d1
--- /dev/null
+++ b/tests/contentcaptureservice/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsContentCaptureServiceTestCases"
+ }
+ ]
+}
diff --git a/tests/contentcaptureservice/aidl/src/android/contentcaptureservice/cts/IOutOfPackageDataSharingService.aidl b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/IOutOfProcessDataSharingService.aidl
similarity index 93%
rename from tests/contentcaptureservice/aidl/src/android/contentcaptureservice/cts/IOutOfPackageDataSharingService.aidl
rename to tests/contentcaptureservice/src/android/contentcaptureservice/cts/IOutOfProcessDataSharingService.aidl
index 110cea6..e3d92e2 100644
--- a/tests/contentcaptureservice/aidl/src/android/contentcaptureservice/cts/IOutOfPackageDataSharingService.aidl
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/IOutOfProcessDataSharingService.aidl
@@ -14,6 +14,6 @@
package android.contentcaptureservice.cts;
-interface IOutOfPackageDataSharingService {
+interface IOutOfProcessDataSharingService {
boolean isContentCaptureManagerAvailable();
}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
index 23153e6..27224c8 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
@@ -21,7 +21,6 @@
import android.app.Service;
import android.content.Intent;
import android.content.LocusId;
-import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
@@ -49,7 +48,14 @@
String mLocusId = "DataShare_CTSTest";
String mMimeType = "application/octet-stream";
- private final IBinder mBinder = new LocalBinder();
+ private final IBinder mBinder = new IOutOfProcessDataSharingService.Stub() {
+ @Override
+ public boolean isContentCaptureManagerAvailable() {
+ ContentCaptureManager manager =
+ getApplicationContext().getSystemService(ContentCaptureManager.class);
+ return manager != null && manager.isContentCaptureEnabled();
+ }
+ };
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
@@ -111,11 +117,4 @@
}
});
}
-
- public class LocalBinder extends Binder {
- OutOfProcessDataSharingService getService() {
- return OutOfProcessDataSharingService.this;
- }
- }
-
}
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ResizingEditActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ResizingEditActivityTest.java
index 03fbc1c..384c959 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ResizingEditActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ResizingEditActivityTest.java
@@ -27,6 +27,7 @@
import android.view.contentcapture.ContentCaptureSessionId;
import androidx.annotation.NonNull;
+import androidx.test.filters.FlakyTest;
import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
@@ -78,6 +79,7 @@
ResizingEditActivity.onRootView(null);
}
+ @FlakyTest(bugId = 162372863)
@Test
public void testInsetsChangedOnImeAction() throws Exception {
final CtsContentCaptureService service = enableService();
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/WhitelistTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/WhitelistTest.java
index 6a91c02..13d6037 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/WhitelistTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/WhitelistTest.java
@@ -92,19 +92,10 @@
}
@Test
- public void testNotWhitelisted_byService_alreadyRunning() throws Exception {
- IOutOfPackageDataSharingService service = getDataShareService();
-
- enableService(NO_PACKAGES, NO_ACTIVITIES);
-
- assertContentCaptureManagerAvailable(service, false);
- }
-
- @Test
public void testWhitelisted_byService_alreadyRunning() throws Exception {
- IOutOfPackageDataSharingService service = getDataShareService();
+ IOutOfProcessDataSharingService service = getDataShareService();
- enableService(toSet(MY_SECOND_PACKAGE), NO_ACTIVITIES);
+ enableService(toSet(MY_PACKAGE), NO_ACTIVITIES);
// Wait for update to propagate
mUiDevice.waitForIdle();
@@ -113,27 +104,6 @@
}
@Test
- public void testRinseAndRepeat_alreadyRunning() throws Exception {
- IOutOfPackageDataSharingService service = getDataShareService();
-
- // No Package
- final CtsContentCaptureService ccService = enableService(NO_PACKAGES, NO_ACTIVITIES);
- assertThat(service.isContentCaptureManagerAvailable()).isEqualTo(false);
-
- // Right package
- ccService.setContentCaptureWhitelist(toSet(MY_SECOND_PACKAGE), NO_ACTIVITIES);
- mUiDevice.waitForIdle();
- assertThat(service.isContentCaptureManagerAvailable()).isEqualTo(true);
-
- // Make sure no-package updates after killing (SystemServiceRegistry caches)
- killService();
- service = getDataShareService();
- ccService.setContentCaptureWhitelist(NO_PACKAGES, NO_ACTIVITIES);
-
- assertContentCaptureManagerAvailable(service, true);
- }
-
- @Test
public void testRinseAndRepeat() throws Exception {
// Right package
@@ -158,7 +128,7 @@
launchActivityAndAssert(service, /* expectHasManager= */ false);
}
- private void assertContentCaptureManagerAvailable(IOutOfPackageDataSharingService service,
+ private void assertContentCaptureManagerAvailable(IOutOfProcessDataSharingService service,
boolean isAvailable) throws Exception {
try {
assertThat(service.isContentCaptureManagerAvailable()).isEqualTo(isAvailable);
@@ -186,18 +156,18 @@
}
}
- private IOutOfPackageDataSharingService getDataShareService() throws Exception {
+ private IOutOfProcessDataSharingService getDataShareService() throws Exception {
Intent outsideService = new Intent();
outsideService.setComponent(new ComponentName(
- "android.contentcaptureservice.cts2",
- "android.contentcaptureservice.cts2.OutOfPackageDataSharingService"
+ "android.contentcaptureservice.cts",
+ "android.contentcaptureservice.cts.OutOfProcessDataSharingService"
));
IBinder service = mServiceRule.bindService(outsideService);
- return IOutOfPackageDataSharingService.Stub.asInterface(service);
+ return IOutOfProcessDataSharingService.Stub.asInterface(service);
}
private void killService() {
runShellCommand("am broadcast --receiver-foreground "
- + "-n android.contentcaptureservice.cts2/.SelfDestructReceiver");
+ + "-n android.contentcaptureservice.cts/.SelfDestructReceiver");
}
}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/AdminPermissionControlParamsTests.java b/tests/devicepolicy/src/android/devicepolicy/cts/AdminPermissionControlParamsTests.java
new file mode 100644
index 0000000..1719c94
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/AdminPermissionControlParamsTests.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.devicepolicy.cts;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.os.Parcel;
+import android.permission.AdminPermissionControlParams;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.bedstead.harrier.annotations.Postsubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AdminPermissionControlParamsTests {
+ private static final String PKG = "somePackage";
+ private static final String PERMISSION = "somePackage";
+ private static final int GRANT_STATE = DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
+ private static final boolean CAN_ADMIN_GRANT = true;
+
+ @Postsubmit(reason="new test")
+ @Test
+ public void gettersReturnConstructorValue() {
+ AdminPermissionControlParams params = createViaParcel(
+ PKG, PERMISSION, GRANT_STATE, CAN_ADMIN_GRANT);
+
+ assertThat(params.getGranteePackageName()).isEqualTo(PKG);
+ assertThat(params.getPermission()).isEqualTo(PERMISSION);
+ assertThat(params.getGrantState()).isEqualTo(GRANT_STATE);
+ assertThat(params.canAdminGrantSensorsPermissions()).isEqualTo(CAN_ADMIN_GRANT);
+ }
+
+ @Postsubmit(reason="new test")
+ @Test
+ public void correctParcelingAndUnparceling() {
+ AdminPermissionControlParams params = createViaParcel();
+
+ Parcel parcel = Parcel.obtain();
+ params.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ AdminPermissionControlParams loadedParams =
+ AdminPermissionControlParams.CREATOR.createFromParcel(parcel);
+
+ assertThat(params.getGranteePackageName()).isEqualTo(loadedParams.getGranteePackageName());
+ assertThat(params.getPermission()).isEqualTo(loadedParams.getPermission());
+ assertThat(params.getGrantState()).isEqualTo(loadedParams.getGrantState());
+ assertThat(params.canAdminGrantSensorsPermissions())
+ .isEqualTo(loadedParams.canAdminGrantSensorsPermissions());
+ }
+
+ private AdminPermissionControlParams createViaParcel(
+ String packageName, String permission, int grantState, boolean canAdminGrant) {
+ Parcel parcel = Parcel.obtain();
+ parcel.writeString(packageName);
+ parcel.writeString(permission);
+ parcel.writeInt(grantState);
+ parcel.writeBoolean(canAdminGrant);
+ parcel.setDataPosition(0);
+
+ return AdminPermissionControlParams.CREATOR.createFromParcel(parcel);
+ }
+
+ private AdminPermissionControlParams createViaParcel() {
+ return createViaParcel(PKG, PERMISSION, GRANT_STATE, CAN_ADMIN_GRANT);
+ }
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java
index 6b19fe4..ff33035 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CredentialManagementAppTest.java
@@ -167,6 +167,33 @@
@Postsubmit(reason="new")
@Test
+ public void hasKeyPair_aliasIsNotInAuthenticationPolicy_throwsException() throws Exception {
+ setCredentialManagementApp();
+
+ try {
+ assertThrows(SecurityException.class, () -> mDpm.hasKeyPair(NOT_IN_USER_POLICY_ALIAS));
+ } finally {
+ removeCredentialManagementApp();
+ }
+ }
+
+ @Postsubmit(reason="new")
+ @Test
+ public void hasKeyPair_isCredentialManagementApp_success() throws Exception {
+ setCredentialManagementApp();
+ try {
+ mDpm.installKeyPair(/* admin = */ null, PRIVATE_KEY, CERTIFICATES, ALIAS,
+ /* flags = */0);
+
+ assertThat(mDpm.hasKeyPair(ALIAS)).isTrue();
+ } finally {
+ mDpm.removeKeyPair(/* admin = */ null, ALIAS);
+ removeCredentialManagementApp();
+ }
+ }
+
+ @Postsubmit(reason="new")
+ @Test
public void removeKeyPair_isCredentialManagementApp_success() throws Exception {
setCredentialManagementApp();
try {
diff --git a/tests/framework/base/biometrics/apps/biometrics/AndroidManifest.xml b/tests/framework/base/biometrics/apps/biometrics/AndroidManifest.xml
index c721983..33ff7b5 100644
--- a/tests/framework/base/biometrics/apps/biometrics/AndroidManifest.xml
+++ b/tests/framework/base/biometrics/apps/biometrics/AndroidManifest.xml
@@ -25,6 +25,8 @@
android:exported="true"/>
<activity android:name="android.server.biometrics.Class2BiometricActivity"
android:exported="true"/>
+ <activity android:name="android.server.biometrics.Class3BiometricActivity"
+ android:exported="true"/>
</application>
</manifest>
\ No newline at end of file
diff --git a/tests/framework/base/biometrics/apps/biometrics/src/android/server/biometrics/Class3BiometricActivity.java b/tests/framework/base/biometrics/apps/biometrics/src/android/server/biometrics/Class3BiometricActivity.java
new file mode 100644
index 0000000..2aaf49c
--- /dev/null
+++ b/tests/framework/base/biometrics/apps/biometrics/src/android/server/biometrics/Class3BiometricActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.biometrics;
+
+import android.app.Activity;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.Executor;
+
+public class Class3BiometricActivity extends Activity {
+ private static final String TAG = "Class3BiometricActivity";
+
+ @Override
+ protected void onCreate(@Nullable Bundle bundle) {
+ super.onCreate(bundle);
+ final Handler handler = new Handler(Looper.getMainLooper());
+ final Executor executor = handler::post;
+ final BiometricCallbackHelper callbackHelper = new BiometricCallbackHelper(this);
+
+ final BiometricPrompt bp = new BiometricPrompt.Builder(this)
+ .setTitle("Title")
+ .setSubtitle("Subtitle")
+ .setDescription("Description")
+ .setNegativeButton("Negative Button", executor, (dialog, which) -> {
+ callbackHelper.onNegativeButtonPressed();
+ })
+ .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
+ .build();
+
+ bp.authenticate(new CancellationSignal(), executor, callbackHelper);
+ }
+}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
index dfd68e5..8c91f3f 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricServiceTest.java
@@ -45,6 +45,9 @@
import android.hardware.biometrics.BiometricTestSession;
import android.hardware.biometrics.SensorProperties;
import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
import android.os.PowerManager;
import android.platform.test.annotations.Presubmit;
import android.server.wm.TestJournalProvider.TestJournal;
@@ -68,8 +71,11 @@
import org.junit.Test;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
@Presubmit
public class BiometricServiceTest extends BiometricTestBase {
@@ -180,6 +186,20 @@
return false;
}
+ private void successfullyAuthenticate(@NonNull BiometricTestSession session, int userId)
+ throws Exception {
+ session.acceptAuthentication(userId);
+ mInstrumentation.waitForIdleSync();
+ waitForStateNotEqual(STATE_AUTH_STARTED_UI_SHOWING);
+ BiometricServiceState state = getCurrentState();
+ Log.d(TAG, "State after acceptAuthentication: " + state);
+ if (state.mState == STATE_AUTH_PENDING_CONFIRM) {
+ findAndPressButton(BUTTON_ID_CONFIRM);
+ mInstrumentation.waitForIdleSync();
+ waitForState(STATE_AUTH_IDLE);
+ }
+ }
+
private void waitForAllUnenrolled() throws Exception {
for (int i = 0; i < 20; i++) {
if (anyEnrollmentsExist()) {
@@ -192,6 +212,38 @@
fail("Some sensors still have enrollments. State: " + getCurrentState());
}
+ private void showBiometricPromptAndAuth(@NonNull BiometricTestSession session, int sensorId,
+ int userId) throws Exception {
+ final Handler handler = new Handler(Looper.getMainLooper());
+ final Executor executor = handler::post;
+ final BiometricPrompt prompt = new BiometricPrompt.Builder(mContext)
+ .setTitle("Title")
+ .setSubtitle("Subtitle")
+ .setDescription("Description")
+ .setNegativeButton("Negative Button", executor, (dialog, which) -> {
+ Log.d(TAG, "Negative button pressed");
+ })
+ .setAllowBackgroundAuthentication(true)
+ .setAllowedSensorIds(new ArrayList<>(Collections.singletonList(sensorId)))
+ .build();
+ prompt.authenticate(new CancellationSignal(), executor,
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
+ Log.d(TAG, "onAuthenticationError: " + errorCode);
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(
+ BiometricPrompt.AuthenticationResult result) {
+ Log.d(TAG, "onAuthenticationSucceeded");
+ }
+ });
+
+ waitForState(STATE_AUTH_STARTED_UI_SHOWING);
+ successfullyAuthenticate(session, userId);
+ }
+
@NonNull
private static BiometricCallbackHelper.State getCallbackState(@NonNull TestJournal journal) {
waitFor("Waiting for authentication callback",
@@ -327,7 +379,8 @@
waitForIdleService();
final BiometricServiceState state = getCurrentState();
- assertEquals(1, state.mSensorStates.sensorStates
+ assertEquals("Sensor: " + sensorId + " should have exactly one enrollment",
+ 1, state.mSensorStates.sensorStates
.get(sensorId).getUserStates().get(userId).numEnrolled);
}
@@ -372,18 +425,7 @@
assertEquals(callbackState.toString(), 0, callbackState.mErrorsReceived.size());
// Auth and check again now
- session.acceptAuthentication(userId);
- mInstrumentation.waitForIdleSync();
-
- waitForStateNotEqual(STATE_AUTH_STARTED_UI_SHOWING);
-
- state = getCurrentState();
- Log.d(TAG, "State after acceptAuthentication: " + state);
- if (state.mState == STATE_AUTH_PENDING_CONFIRM) {
- findAndPressButton(BUTTON_ID_CONFIRM);
- mInstrumentation.waitForIdleSync();
- waitForState(STATE_AUTH_IDLE);
- }
+ successfullyAuthenticate(session, userId);
mInstrumentation.waitForIdleSync();
callbackState = getCallbackState(journal);
@@ -692,6 +734,206 @@
}
}
+ @Test
+ public void testLockoutResetRequestedAfterCredentialUnlock() throws Exception {
+ // ResetLockout only really needs to be applied when enrollments exist. Furthermore, some
+ // interfaces may take this a step further and ignore resetLockout requests when no
+ // enrollments exist.
+ List<BiometricTestSession> biometricSessions = new ArrayList<>();
+ for (SensorProperties prop : mSensorProperties) {
+ BiometricTestSession session = mBiometricManager.createTestSession(prop.getSensorId());
+ enrollForSensor(session, prop.getSensorId());
+ biometricSessions.add(session);
+ }
+
+ try (CredentialSession credentialSession = new CredentialSession()) {
+ credentialSession.setCredential();
+
+ // Explicitly clear the state so we can check exact number below
+ final BiometricServiceState clearState = getCurrentStateAndClearSchedulerLog();
+ credentialSession.verifyCredential();
+
+ waitFor("Waiting for password verification and resetLockout completion", () -> {
+ try {
+ BiometricServiceState state = getCurrentState();
+ // All sensors have processed exactly one resetLockout request. Use a boolean
+ // to track this so we have better logging
+ boolean allResetOnce = true;
+ for (SensorProperties prop : mSensorProperties) {
+ final int numResetLockouts = Utils.numberOfSpecifiedOperations(state,
+ prop.getSensorId(), BiometricsProto.CM_RESET_LOCKOUT);
+ Log.d(TAG, "Sensor: " + prop.getSensorId()
+ + ", numResetLockouts: " + numResetLockouts);
+ if (numResetLockouts != 1) {
+ allResetOnce = false;
+ }
+ }
+ return allResetOnce;
+ } catch (Exception e) {
+ return false;
+ }
+ }, unused -> fail("All sensors must receive and process exactly one resetLockout"));
+ }
+
+ for (BiometricTestSession session : biometricSessions) {
+ session.close();
+ }
+ }
+
+ @Test
+ public void testLockoutResetRequestedAfterBiometricUnlock_whenStrong() throws Exception {
+ assumeTrue(mSensorProperties.size() > 1);
+
+ // ResetLockout only really needs to be applied when enrollments exist. Furthermore, some
+ // interfaces may take this a step further and ignore resetLockout requests when no
+ // enrollments exist.
+ Map<Integer, BiometricTestSession> biometricSessions = new HashMap<>();
+ for (SensorProperties prop : mSensorProperties) {
+ BiometricTestSession session = mBiometricManager.createTestSession(prop.getSensorId());
+ enrollForSensor(session, prop.getSensorId());
+ biometricSessions.put(prop.getSensorId(), session);
+ }
+
+ // When a strong biometric sensor authenticates, all other biometric sensors that:
+ // 1) Do not require HATs for resetLockout (e.g. IBiometricsFingerprint@2.1) or
+ // 2) Require HATs but do not require challenges (e.g. IFingerprint@1.0, IFace@1.0)
+ // schedule and complete a resetLockout operation.
+ //
+ // To be more explicit, sensors that require HATs AND challenges (IBiometricsFace@1.0)
+ // do not schedule resetLockout, since the interface has no way of generating multiple
+ // HATs with a single authentication (e.g. if the user requested to unlock an auth-bound
+ // key, the only HAT returned would have the keystore operationId within).
+ for (SensorProperties prop : mSensorProperties) {
+ if (prop.getSensorStrength() != SensorProperties.STRENGTH_STRONG) {
+ Log.d(TAG, "Skipping sensor: " + prop.getSensorId()
+ + ", strength: " + prop.getSensorStrength());
+ continue;
+ }
+ testLockoutResetRequestedAfterBiometricUnlock_whenStrong_forSensor(
+ prop.getSensorId(), biometricSessions.get(prop.getSensorId()));
+ }
+
+ for (BiometricTestSession session : biometricSessions.values()) {
+ session.close();
+ }
+ }
+
+ private void testLockoutResetRequestedAfterBiometricUnlock_whenStrong_forSensor(int sensorId,
+ @NonNull BiometricTestSession session)
+ throws Exception {
+ Log.d(TAG, "testLockoutResetRequestedAfterBiometricUnlock_whenStrong_forSensor: "
+ + sensorId);
+ final int userId = 0;
+
+ BiometricServiceState state = getCurrentState();
+ final List<Integer> eligibleSensorsToReset = new ArrayList<>();
+ final List<Integer> ineligibleSensorsToReset = new ArrayList<>();
+ for (SensorProperties prop : mSensorProperties) {
+ if (prop.getSensorId() == sensorId) {
+ // Do not need to resetLockout for self
+ continue;
+ }
+
+ SensorState sensorState = state.mSensorStates.sensorStates.get(prop.getSensorId());
+ final boolean supportsChallengelessHat =
+ sensorState.isResetLockoutRequiresHardwareAuthToken()
+ && !sensorState.isResetLockoutRequiresChallenge();
+ final boolean doesNotRequireHat =
+ !sensorState.isResetLockoutRequiresHardwareAuthToken();
+ Log.d(TAG, "SensorId: " + prop.getSensorId()
+ + ", supportsChallengelessHat: " + supportsChallengelessHat
+ + ", doesNotRequireHat: " + doesNotRequireHat);
+ if (supportsChallengelessHat || doesNotRequireHat) {
+ Log.d(TAG, "Adding eligible sensor: " + prop.getSensorId());
+ eligibleSensorsToReset.add(prop.getSensorId());
+ } else {
+ Log.d(TAG, "Adding ineligible sensor: " + prop.getSensorId());
+ ineligibleSensorsToReset.add(prop.getSensorId());
+ }
+ }
+
+ // Explicitly clear the log so that we can check the exact number of resetLockout operations
+ // below.
+ state = getCurrentStateAndClearSchedulerLog();
+
+ // Request authentication with the specified sensorId that was passed in
+ showBiometricPromptAndAuth(session, sensorId, userId);
+
+ // Check that all eligible sensors have resetLockout in their scheduler history
+ state = getCurrentState();
+ for (Integer id : eligibleSensorsToReset) {
+ assertEquals("Sensor: " + id + " should have exactly one resetLockout", 1,
+ Utils.numberOfSpecifiedOperations(state, id, BiometricsProto.CM_RESET_LOCKOUT));
+ }
+
+ // Check that all ineligible sensors do not have resetLockout in their scheduler history
+ for (Integer id : ineligibleSensorsToReset) {
+ assertEquals("Sensor: " + id + " should have no resetLockout", 0,
+ Utils.numberOfSpecifiedOperations(state, id, BiometricsProto.CM_RESET_LOCKOUT));
+ }
+ }
+
+ @Test
+ public void testLockoutResetNotRequestedAfterBiometricUnlock_whenNotStrong() throws Exception {
+ assumeTrue(mSensorProperties.size() > 1);
+
+ // ResetLockout only really needs to be applied when enrollments exist. Furthermore, some
+ // interfaces may take this a step further and ignore resetLockout requests when no
+ // enrollments exist.
+ Map<Integer, BiometricTestSession> biometricSessions = new HashMap<>();
+ for (SensorProperties prop : mSensorProperties) {
+ BiometricTestSession session = mBiometricManager.createTestSession(prop.getSensorId());
+ enrollForSensor(session, prop.getSensorId());
+ biometricSessions.put(prop.getSensorId(), session);
+ }
+
+ // Sensors that do not meet BIOMETRIC_STRONG are not allowed to resetLockout for other
+ // sensors.
+ // TODO: Note that we are only testing STRENGTH_WEAK for now, since STRENGTH_CONVENIENCE is
+ // not exposed to BiometricPrompt. In other words, we currently do not have a way to
+ // request and finish authentication for STRENGTH_CONVENIENCE sensors.
+ for (SensorProperties prop : mSensorProperties) {
+ if (prop.getSensorStrength() != SensorProperties.STRENGTH_WEAK) {
+ Log.d(TAG, "Skipping sensor: " + prop.getSensorId()
+ + ", strength: " + prop.getSensorStrength());
+ continue;
+ }
+
+ testLockoutResetNotRequestedAfterBiometricUnlock_whenNotStrong_forSensor(
+ prop.getSensorId(), biometricSessions.get(prop.getSensorId()));
+ }
+
+ // Cleanup
+ for (BiometricTestSession s : biometricSessions.values()) {
+ s.close();
+ }
+ }
+
+ private void testLockoutResetNotRequestedAfterBiometricUnlock_whenNotStrong_forSensor(
+ int sensorId, @NonNull BiometricTestSession session) throws Exception {
+ Log.d(TAG, "testLockoutResetNotRequestedAfterBiometricUnlock_whenNotStrong_forSensor: "
+ + sensorId);
+ final int userId = 0;
+
+ // Explicitly clear the log so that we can check the exact number of resetLockout operations
+ // below.
+ BiometricServiceState state = getCurrentStateAndClearSchedulerLog();
+
+ // Request authentication with the specified sensorId that was passed in
+ showBiometricPromptAndAuth(session, sensorId, userId);
+
+ // Check that no other sensors have resetLockout in their queue
+ for (SensorProperties prop : mSensorProperties) {
+ if (prop.getSensorId() == sensorId) {
+ continue;
+ }
+ state = getCurrentState();
+ assertEquals("Sensor: " + prop.getSensorId() + " should have no resetLockout", 0,
+ Utils.numberOfSpecifiedOperations(state, prop.getSensorId(),
+ BiometricsProto.CM_RESET_LOCKOUT));
+ }
+ }
+
@Override
protected SensorStates getSensorStates() throws Exception {
return getCurrentState().mSensorStates;
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
index f641de5..2532d2e 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
@@ -21,8 +21,10 @@
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
/**
* Base class for biometric tests, containing common useful logic.
@@ -62,8 +64,14 @@
}
protected static void waitFor(@NonNull String message, @NonNull BooleanSupplier condition) {
+ waitFor(message, condition, null /* onFailure */);
+ }
+
+ protected static void waitFor(@NonNull String message, @NonNull BooleanSupplier condition,
+ @Nullable Consumer<Object> onFailure) {
Condition.waitFor(new Condition<>(message, condition)
.setRetryIntervalMs(500)
- .setRetryLimit(20));
+ .setRetryLimit(20)
+ .setOnFailure(onFailure));
}
}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/Components.java b/tests/framework/base/biometrics/src/android/server/biometrics/Components.java
index 970e11a..8734436 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/Components.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/Components.java
@@ -24,6 +24,8 @@
component("Class2BiometricOrCredentialActivity");
public static final ComponentName CLASS_2_BIOMETRIC_ACTIVITY =
component("Class2BiometricActivity");
+ public static final ComponentName CLASS_3_BIOMETRIC_ACTIVITY =
+ component("Class3BiometricActivity");
private static ComponentName component(String className) {
return component(Components.class, className);
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/CredentialSession.java b/tests/framework/base/biometrics/src/android/server/biometrics/CredentialSession.java
index b60a661..c8e4871 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/CredentialSession.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/CredentialSession.java
@@ -20,11 +20,16 @@
private static final String SET_PASSWORD = "locksettings set-pin 1234";
private static final String CLEAR_PASSWORD = "locksettings clear --old 1234";
+ private static final String VERIFY_CREDENTIAL = "locksettings verify --old 1234";
public void setCredential() {
Utils.executeShellCommand(SET_PASSWORD);
}
+ public void verifyCredential() {
+ Utils.executeShellCommand(VERIFY_CREDENTIAL);
+ }
+
@Override
public void close() throws Exception {
Utils.executeShellCommand(CLEAR_PASSWORD);
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java b/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java
index 0e86403..f0b5cf2 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/SensorStates.java
@@ -72,12 +72,18 @@
private final SchedulerState mSchedulerState;
private final int mModality;
@NonNull private final Map<Integer, UserState> mUserStates;
+ private final boolean mResetLockoutRequiresHardwareAuthToken;
+ private final boolean mResetLockoutRequiresChallenge;
public SensorState(@NonNull SchedulerState schedulerState, int modality,
- @NonNull Map<Integer, UserState> userStates) {
+ @NonNull Map<Integer, UserState> userStates,
+ boolean resetLockoutRequiresHardwareAuthToken,
+ boolean resetLockoutRequiresChallenge) {
this.mSchedulerState = schedulerState;
this.mModality = modality;
this.mUserStates = userStates;
+ this.mResetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
+ this.mResetLockoutRequiresChallenge = resetLockoutRequiresChallenge;
}
public SchedulerState getSchedulerState() {
@@ -95,6 +101,14 @@
@NonNull public Map<Integer, UserState> getUserStates() {
return mUserStates;
}
+
+ public boolean isResetLockoutRequiresHardwareAuthToken() {
+ return mResetLockoutRequiresHardwareAuthToken;
+ }
+
+ public boolean isResetLockoutRequiresChallenge() {
+ return mResetLockoutRequiresChallenge;
+ }
}
public static class UserState {
@@ -118,7 +132,9 @@
final SchedulerState schedulerState =
SchedulerState.parseFrom(sensorStateProto.scheduler);
final SensorState sensorState = new SensorState(schedulerState,
- sensorStateProto.modality, userStates);
+ sensorStateProto.modality, userStates,
+ sensorStateProto.resetLockoutRequiresHardwareAuthToken,
+ sensorStateProto.resetLockoutRequiresChallenge);
sensorStates.put(sensorStateProto.sensorId, sensorState);
}
diff --git a/tests/framework/base/windowmanager/app/res/values/styles.xml b/tests/framework/base/windowmanager/app/res/values/styles.xml
index 73dcc55..5c8e4f8 100644
--- a/tests/framework/base/windowmanager/app/res/values/styles.xml
+++ b/tests/framework/base/windowmanager/app/res/values/styles.xml
@@ -82,5 +82,6 @@
</style>
<style name="ShowBrandingTheme" parent="@android:style/Theme.Material.NoActionBar">
<item name="android:windowSplashScreenBrandingImage">@drawable/branding</item>
+ <item name="android:windowSplashScreenIconBackgroundColor">@drawable/blue</item>
</style>
</resources>
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 e7fd1bd..09bb346 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
@@ -275,6 +275,7 @@
public static final String RECEIVE_SPLASH_SCREEN_EXIT = "receive_splash_screen_exit";
public static final String CONTAINS_CENTER_VIEW = "contains_center_view";
public static final String CONTAINS_BRANDING_VIEW = "contains_branding_view";
+ public static final String ICON_BACKGROUND_COLOR = "icon_background_color";
public static final String ICON_ANIMATION_DURATION = "icon_animation_duration";
public static final String ICON_ANIMATION_START = "icon_animation_start";
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java
index bf82ea2..ad0e5ae 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java
@@ -22,6 +22,7 @@
import static android.server.wm.app.Components.TestStartingWindowKeys.CONTAINS_CENTER_VIEW;
import static android.server.wm.app.Components.TestStartingWindowKeys.GET_NIGHT_MODE_ACTIVITY_CHANGED;
import static android.server.wm.app.Components.TestStartingWindowKeys.HANDLE_SPLASH_SCREEN_EXIT;
+import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_BACKGROUND_COLOR;
import static android.server.wm.app.Components.TestStartingWindowKeys.RECEIVE_SPLASH_SCREEN_EXIT;
import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_HANDLE_EXIT_ON_CREATE;
import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_HANDLE_EXIT_ON_RESUME;
@@ -61,10 +62,12 @@
final boolean containsCenter = view.getIconView() != null;
final boolean containsBranding = view.getBrandingView() != null
&& view.getBrandingView().getBackground() != null;
+ final int iconBackground = view.getIconBackgroundColor();
TestJournalProvider.putExtras(baseContext, HANDLE_SPLASH_SCREEN_EXIT, bundle -> {
bundle.putBoolean(RECEIVE_SPLASH_SCREEN_EXIT, true);
bundle.putBoolean(CONTAINS_CENTER_VIEW, containsCenter);
bundle.putBoolean(CONTAINS_BRANDING_VIEW, containsBranding);
+ bundle.putInt(ICON_BACKGROUND_COLOR, iconBackground);
});
view.postDelayed(view::remove, 500);
};
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java
index 8db71ef..15c31d8 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java
@@ -45,6 +45,7 @@
mSSM = getSplashScreen();
if (getIntent().getBooleanExtra(REQUEST_HANDLE_EXIT_ON_CREATE, false)) {
mSSM.setOnExitAnimationListener(mSplashScreenExitHandler);
+ SystemClock.sleep(500);
}
if (getIntent().getBooleanExtra(DELAY_RESUME, false)) {
SystemClock.sleep(5000);
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 0b91a9f..f67be77 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
@@ -290,6 +290,35 @@
}
@Test
+ public void testActivityNotBlockedFromBgActivityInFgTask() {
+ // Launch Activity A, B in the same task with different processes.
+ final Intent intent = new Intent()
+ .setComponent(APP_A_FOREGROUND_ACTIVITY)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ mWmState.waitForValidState(APP_A_FOREGROUND_ACTIVITY);
+ mContext.sendBroadcast(getLaunchActivitiesBroadcast(APP_B_FOREGROUND_ACTIVITY));
+ mWmState.waitForValidState(APP_B_FOREGROUND_ACTIVITY);
+ assertTaskStack(new ComponentName[]{APP_B_FOREGROUND_ACTIVITY, APP_A_FOREGROUND_ACTIVITY},
+ APP_A_FOREGROUND_ACTIVITY);
+
+ // Refresh last-stop-app-switch-time by returning to home and then make the task foreground.
+ pressHomeAndResumeAppSwitch();
+ mContext.startActivity(intent);
+ mWmState.waitForValidState(APP_B_FOREGROUND_ACTIVITY);
+ // Though process A is in background, it is in a visible Task (top is B) so it should be
+ // able to start activity successfully.
+ mContext.sendBroadcast(new Intent(ACTION_LAUNCH_BACKGROUND_ACTIVITIES)
+ .putExtra(LAUNCH_INTENTS_EXTRA, new Intent[]{ new Intent()
+ .setComponent(APP_A_BACKGROUND_ACTIVITY)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }));
+ mWmState.waitForValidState(APP_A_BACKGROUND_ACTIVITY);
+ mWmState.assertFocusedActivity(
+ "The background activity must be able to launch from a visible task",
+ APP_A_BACKGROUND_ACTIVITY);
+ }
+
+ @Test
@FlakyTest(bugId = 130800326)
@Ignore // TODO(b/145981637): Make this test work
public void testActivityBlockedWhenForegroundActivityRestartsItself() throws Exception {
diff --git a/tests/framework/base/windowmanager/dndtargetapp/res/layout/target_activity.xml b/tests/framework/base/windowmanager/dndtargetapp/res/layout/target_activity.xml
index 66a8154..731bfb1 100644
--- a/tests/framework/base/windowmanager/dndtargetapp/res/layout/target_activity.xml
+++ b/tests/framework/base/windowmanager/dndtargetapp/res/layout/target_activity.xml
@@ -32,4 +32,17 @@
android:gravity="center"
android:visibility="gone">
</EditText>
+ <LinearLayout
+ android:id="@+id/linearlayout_drag_target"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:visibility="gone">
+ <TextView
+ android:id="@+id/textview_in_drag_target"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center">
+ </TextView>
+ </LinearLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java b/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java
index ceb8816..af4fd00 100644
--- a/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java
+++ b/tests/framework/base/windowmanager/dndtargetapp/src/android/server/wm/dndtargetapp/DropTarget.java
@@ -30,6 +30,7 @@
import android.view.DragEvent;
import android.view.OnReceiveContentListener;
import android.view.View;
+import android.widget.LinearLayout;
import android.widget.TextView;
public class DropTarget extends Activity {
@@ -68,7 +69,12 @@
setUpDropTarget("request_write", new OnDragUriWriteListener());
setUpDropTarget("request_read_nested", new OnDragUriReadPrefixListener());
setUpDropTarget("request_take_persistable", new OnDragUriTakePersistableListener());
- setUpDropTarget("on_receive_content_listener", new UriReadOnReceiveContentListener());
+ setUpDropTarget("textview_on_receive_content_listener",
+ new UriReadOnReceiveContentListener());
+ setUpDropTarget("edittext_on_receive_content_listener",
+ new UriReadOnReceiveContentListener());
+ setUpDropTarget("linearlayout_on_receive_content_listener",
+ new UriReadOnReceiveContentListener());
}
private void setUpDropTarget(String mode, OnDragUriListener listener) {
@@ -84,14 +90,29 @@
if (!mode.equals(getIntent().getStringExtra("mode"))) {
return;
}
- // Use an EditText widget when testing OnReceiveContentListener, since a TextView must
- // be editable in order to accept DragEvents by default.
- TextView nonEditableTextView = findViewById(R.id.drag_target);
- nonEditableTextView.setVisibility(View.GONE);
- mTextView = findViewById(R.id.editable_drag_target);
- mTextView.setVisibility(View.VISIBLE);
+ TextView defaultDropTarget = findViewById(R.id.drag_target);
+ String typeOfViewToTest = mode.substring(0, mode.indexOf('_'));
+ View dropTarget;
+ switch (typeOfViewToTest) {
+ case "textview":
+ mTextView = defaultDropTarget;
+ dropTarget = mTextView;
+ break;
+ case "edittext":
+ defaultDropTarget.setVisibility(View.GONE);
+ mTextView = findViewById(R.id.editable_drag_target);
+ dropTarget = mTextView;
+ break;
+ case "linearlayout":
+ defaultDropTarget.setVisibility(View.GONE);
+ mTextView = findViewById(R.id.textview_in_drag_target);
+ dropTarget = findViewById(R.id.linearlayout_drag_target);
+ break;
+ default: throw new IllegalArgumentException("Invalid mode: " + mode);
+ }
mTextView.setText(mode);
- mTextView.setOnReceiveContentListener(new String[] {"text/*", "image/*"}, listener);
+ dropTarget.setVisibility(View.VISIBLE);
+ dropTarget.setOnReceiveContentListener(new String[] {"text/*", "image/*"}, listener);
}
private String checkExtraValue(DragEvent event) {
diff --git a/tests/framework/base/windowmanager/intent_tests/newDocumentCases/test-13.json b/tests/framework/base/windowmanager/intent_tests/newDocumentCases/test-13.json
new file mode 100644
index 0000000..e840860
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newDocumentCases/test-13.json
@@ -0,0 +1,49 @@
+{
+ "comment": "Verify that a new task should not be created when the DocumentLaunchNeverActivity was started, even if the Intent contains FLAG_ACTIVITY_NEW_DOCUMENT and FLAG_ACTIVITY_MULTIPLE_TASK",
+ "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_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK",
+ "class": "android.server.wm.intent.Activities$DocumentLaunchNeverActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+ "state": "RESUMED"
+ }
+ ]
+ }
+ ]
+ },
+ "endState": {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts/android.server.wm.intent.Activities$DocumentLaunchNeverActivity",
+ "state": "RESUMED"
+ },
+ {
+ "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/intent_tests/newDocumentCases/test-14.json b/tests/framework/base/windowmanager/intent_tests/newDocumentCases/test-14.json
new file mode 100644
index 0000000..76e16fa
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newDocumentCases/test-14.json
@@ -0,0 +1,69 @@
+{
+ "comment": "Verify that reuse the existing DocumentLaunchNeverActivity while launch the activity again",
+ "setup": {
+ "initialIntents": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_TASK",
+ "class": "android.server.wm.intent.Activities$DocumentLaunchNeverActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ },
+ {
+ "flags": "",
+ "class": "android.server.wm.intent.Activities$SingleInstanceActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ],
+ "act": [
+ {
+ "flags": "FLAG_ACTIVITY_NEW_DOCUMENT",
+ "class": "android.server.wm.intent.Activities$DocumentLaunchNeverActivity",
+ "package": "android.server.wm.cts",
+ "startForResult": false
+ }
+ ]
+ },
+ "initialState": {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts/android.server.wm.intent.Activities$SingleInstanceActivity",
+ "state": "RESUMED"
+ }
+ ]
+ },
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts/android.server.wm.intent.Activities$DocumentLaunchNeverActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ]
+
+ },
+ "endState": {
+ "tasks": [
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts/android.server.wm.intent.Activities$DocumentLaunchNeverActivity",
+ "state": "RESUMED"
+ }
+ ]
+ },
+ {
+ "activities": [
+ {
+ "name": "android.server.wm.cts/android.server.wm.intent.Activities$SingleInstanceActivity",
+ "state": "STOPPED"
+ }
+ ]
+ }
+ ]
+
+ }
+}
\ No newline at end of file
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 2befd739..2052272 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
@@ -40,6 +40,8 @@
import android.util.Log;
import android.view.Display;
+import com.google.common.collect.ImmutableSet;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -73,7 +75,18 @@
private static final String REQUEST_READ_NESTED = "request_read_nested";
private static final String REQUEST_TAKE_PERSISTABLE = "request_take_persistable";
private static final String REQUEST_WRITE = "request_write";
- private static final String TARGET_ON_RECEIVE_CONTENT_LISTENER = "on_receive_content_listener";
+
+ private static final String TARGET_ON_RECEIVE_CONTENT_LISTENER_TEXT_VIEW =
+ "textview_on_receive_content_listener";
+ private static final String TARGET_ON_RECEIVE_CONTENT_LISTENER_EDIT_TEXT =
+ "edittext_on_receive_content_listener";
+ private static final String TARGET_ON_RECEIVE_CONTENT_LISTENER_LINEAR_LAYOUT =
+ "linearlayout_on_receive_content_listener";
+ private static final ImmutableSet<String> ON_RECEIVE_CONTENT_LISTENER_MODES = ImmutableSet.of(
+ TARGET_ON_RECEIVE_CONTENT_LISTENER_TEXT_VIEW,
+ TARGET_ON_RECEIVE_CONTENT_LISTENER_EDIT_TEXT,
+ TARGET_ON_RECEIVE_CONTENT_LISTENER_LINEAR_LAYOUT
+ );
private static final String SOURCE_LOG_TAG = "DragSource";
private static final String TARGET_LOG_TAG = "DropTarget";
@@ -230,7 +243,7 @@
// Skip the following assertions when testing OnReceiveContentListener, since it only
// handles drop events.
- if (!TARGET_ON_RECEIVE_CONTENT_LISTENER.equals(targetMode)) {
+ if (!ON_RECEIVE_CONTENT_LISTENER_MODES.contains(targetMode)) {
if (!RESULT_MISSING.equals(expectedDropResult)) {
assertTargetResult(RESULT_KEY_ACCESS_BEFORE, RESULT_EXCEPTION);
assertTargetResult(RESULT_KEY_ACCESS_AFTER, RESULT_EXCEPTION);
@@ -361,12 +374,35 @@
}
@Test
- public void testOnReceiveContentListener_GrantRead() throws Exception {
- assertDropResult(GRANT_READ, TARGET_ON_RECEIVE_CONTENT_LISTENER, RESULT_OK);
+ public void testOnReceiveContentListener_TextView_GrantRead() throws Exception {
+ assertDropResult(GRANT_READ, TARGET_ON_RECEIVE_CONTENT_LISTENER_TEXT_VIEW, RESULT_OK);
}
@Test
- public void testOnReceiveContentListener_GrantNone() throws Exception {
- assertDropResult(GRANT_NONE, TARGET_ON_RECEIVE_CONTENT_LISTENER, RESULT_EXCEPTION);
+ public void testOnReceiveContentListener_TextView_GrantNone() throws Exception {
+ assertDropResult(GRANT_NONE, TARGET_ON_RECEIVE_CONTENT_LISTENER_TEXT_VIEW,
+ RESULT_EXCEPTION);
+ }
+
+ @Test
+ public void testOnReceiveContentListener_EditText_GrantRead() throws Exception {
+ assertDropResult(GRANT_READ, TARGET_ON_RECEIVE_CONTENT_LISTENER_EDIT_TEXT, RESULT_OK);
+ }
+
+ @Test
+ public void testOnReceiveContentListener_EditText_GrantNone() throws Exception {
+ assertDropResult(GRANT_NONE, TARGET_ON_RECEIVE_CONTENT_LISTENER_EDIT_TEXT,
+ RESULT_EXCEPTION);
+ }
+
+ @Test
+ public void testOnReceiveContentListener_LinearLayout_GrantRead() throws Exception {
+ assertDropResult(GRANT_READ, TARGET_ON_RECEIVE_CONTENT_LISTENER_LINEAR_LAYOUT, RESULT_OK);
+ }
+
+ @Test
+ public void testOnReceiveContentListener_LinearLayout_GrantNone() throws Exception {
+ assertDropResult(GRANT_NONE, TARGET_ON_RECEIVE_CONTENT_LISTENER_LINEAR_LAYOUT,
+ RESULT_EXCEPTION);
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
index 93cbaee..e73bd0c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
@@ -21,6 +21,7 @@
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -279,6 +280,29 @@
}
@Test
+ public void testVerifyDisplayHash_ValidDisplayHash() {
+ mInstrumentation.runOnMainSync(() -> {
+ final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
+ mTestViewSize.y);
+ mTestView = new View(mActivity);
+ mTestView.setBackgroundColor(Color.BLUE);
+ mMainView.addView(mTestView, p);
+ mMainView.invalidate();
+ });
+ mInstrumentation.waitForIdleSync();
+
+ DisplayHash displayHash = generateDisplayHash(null);
+ VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
+ displayHash);
+
+ assertNotNull(verifiedDisplayHash);
+ assertEquals(displayHash.getTimeMillis(), verifiedDisplayHash.getTimeMillis());
+ assertEquals(displayHash.getBoundsInWindow(), verifiedDisplayHash.getBoundsInWindow());
+ assertEquals(displayHash.getHashAlgorithm(), verifiedDisplayHash.getHashAlgorithm());
+ assertArrayEquals(displayHash.getImageHash(), verifiedDisplayHash.getImageHash());
+ }
+
+ @Test
public void testVerifyDisplayHash_InvalidDisplayHash() {
mInstrumentation.runOnMainSync(() -> {
final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
@@ -296,9 +320,25 @@
displayHash.getHashAlgorithm(), new byte[32], displayHash.getHmac());
VerifiedDisplayHash verifiedDisplayHash = mDisplayHashManager.verifyDisplayHash(
fakeDisplayHash);
+
assertNull(verifiedDisplayHash);
}
+ @Test
+ public void testVerifiedDisplayHash() {
+ long timeMillis = 1000;
+ Rect boundsInWindow = new Rect(0, 0, 50, 100);
+ String hashAlgorithm = "hashAlgorithm";
+ byte[] imageHash = new byte[]{2, 4, 1, 5, 6, 2};
+ VerifiedDisplayHash verifiedDisplayHash = new VerifiedDisplayHash(timeMillis,
+ boundsInWindow, hashAlgorithm, imageHash);
+
+ assertEquals(timeMillis, verifiedDisplayHash.getTimeMillis());
+ assertEquals(boundsInWindow, verifiedDisplayHash.getBoundsInWindow());
+ assertEquals(hashAlgorithm, verifiedDisplayHash.getHashAlgorithm());
+ assertArrayEquals(imageHash, verifiedDisplayHash.getImageHash());
+ }
+
private DisplayHash generateDisplayHash(Rect bounds) {
mTestView.generateDisplayHash(mFirstHashAlgorithm, bounds, mExecutor,
mSyncDisplayHashResultCallback);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/InputMethodVisibilityVerifier.java b/tests/framework/base/windowmanager/src/android/server/wm/InputMethodVisibilityVerifier.java
new file mode 100644
index 0000000..5cd5060
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/InputMethodVisibilityVerifier.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.SystemClock;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.mockime.Watermark;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+/**
+ * Provides utility methods to test whether test IMEs are visible to the user or not.
+ */
+public final class InputMethodVisibilityVerifier {
+
+ private static final long SCREENSHOT_DELAY = 100; // msec
+ private static final long SCREENSHOT_TIME_SLICE = 500; // msec
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private InputMethodVisibilityVerifier() {
+ }
+
+ @NonNull
+ private static boolean containsWatermark(@NonNull UiAutomation uiAutomation) {
+ return Watermark.detect(uiAutomation.takeScreenshot());
+ }
+
+ @NonNull
+ private static boolean notContainsWatermark(@NonNull UiAutomation uiAutomation) {
+ return !Watermark.detect(uiAutomation.takeScreenshot());
+ }
+
+ private static boolean waitUntil(long timeout, @NonNull Predicate<UiAutomation> condition) {
+ final long startTime = SystemClock.elapsedRealtime();
+ SystemClock.sleep(SCREENSHOT_DELAY);
+
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+ // Wait until the main thread becomes idle.
+ final CountDownLatch latch = new CountDownLatch(1);
+ instrumentation.waitForIdle(latch::countDown);
+ try {
+ if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
+ return false;
+ }
+ } catch (InterruptedException e) {
+ }
+
+ final UiAutomation uiAutomation = instrumentation.getUiAutomation();
+ if (condition.test(uiAutomation)) {
+ return true;
+ }
+ while ((SystemClock.elapsedRealtime() - startTime) < timeout) {
+ SystemClock.sleep(SCREENSHOT_TIME_SLICE);
+ if (condition.test(uiAutomation)) {
+ return true;
+ }
+ }
+ return condition.test(uiAutomation);
+ }
+
+ /**
+ * Asserts that {@link com.android.cts.mockime.MockIme} is visible to the user.
+ *
+ * <p>This never succeeds when
+ * {@link com.android.cts.mockime.ImeSettings.Builder#setWatermarkEnabled(boolean)} is
+ * explicitly called with {@code false}.</p>
+ *
+ * @param timeout timeout in milliseconds.
+ */
+ public static void expectImeVisible(long timeout) {
+ assertTrue(waitUntil(timeout, InputMethodVisibilityVerifier::containsWatermark));
+ }
+
+ /**
+ * Asserts that {@link com.android.cts.mockime.MockIme} is not visible to the user.
+ *
+ * <p>This always succeeds when
+ * {@link com.android.cts.mockime.ImeSettings.Builder#setWatermarkEnabled(boolean)} is
+ * explicitly called with {@code false}.</p>
+ *
+ * @param timeout timeout in milliseconds.
+ */
+ public static void expectImeInvisible(long timeout) {
+ assertTrue(waitUntil(timeout, InputMethodVisibilityVerifier::notContainsWatermark));
+ }
+}
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 5e7e90a..4d44076 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
@@ -201,6 +201,11 @@
*/
@Test
public void testLaunchExternalDisplayActivityWhilePrimaryOff() {
+ if (isOperatorTierDevice()) {
+ // This test is not applicable for the device who uses launch_after_boot configuration
+ return;
+ }
+
// Launch something on the primary display so we know there is a resumed activity there
launchActivity(RESIZEABLE_ACTIVITY);
waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
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 c1273e5..8dbc12c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
@@ -619,6 +619,69 @@
NOT_EXPECT_TIMEOUT);
}
+ /**
+ * Test that the IME remains hidden with the {@link WindowManager#DISPLAY_IME_POLICY_HIDE} flag
+ * if the user taps the EditText on displays with no system decorations.
+ */
+ @Test
+ public void testDisplayPolicyImeHideImeNoSystemDecorations() throws Exception {
+ assumeTrue(MSG_NO_MOCK_IME, supportsInstallableIme());
+
+ final MockImeSession mockImeSession = createManagedMockImeSession(this);
+
+ // Launch Ime test activity on default display.
+ final TestActivitySession<ImeTestActivity2> defaultDisplaySession =
+ createManagedTestActivitySession();
+ defaultDisplaySession.launchTestActivityOnDisplaySync(ImeTestActivity2.class,
+ DEFAULT_DISPLAY);
+
+ // Tap the EditText to start IME session.
+ final int[] location = new int[2];
+ EditText editText = defaultDisplaySession.getActivity().mEditText;
+ tapOnDisplayCenter(DEFAULT_DISPLAY);
+ editText.getLocationOnScreen(location);
+ tapOnDisplaySync(location[0], location[1], DEFAULT_DISPLAY);
+
+ // Verify the activity shows soft input on the default display.
+ final ImeEventStream stream = mockImeSession.openEventStream();
+ waitOrderedImeEventsThenAssertImeShown(stream, DEFAULT_DISPLAY,
+ editorMatcher("onStartInput", editText.getPrivateImeOptions()),
+ event -> "showSoftInput".equals(event.getEventName()));
+
+ // Create a virtual display with the policy to hide the IME.
+ final DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setShowSystemDecorations(false)
+ .setDisplayImePolicy(DISPLAY_IME_POLICY_HIDE)
+ .setSimulateDisplay(true)
+ .createDisplay();
+
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> assertTrue("Display should not support showing IME window",
+ mTargetContext.getSystemService(WindowManager.class)
+ .getDisplayImePolicy(newDisplay.mId)
+ == DISPLAY_IME_POLICY_HIDE));
+
+ final TestActivitySession<ImeTestActivity> imeTestActivitySession =
+ createManagedTestActivitySession();
+
+ // Launch Ime test activity in virtual display.
+ imeTestActivitySession.launchTestActivityOnDisplay(ImeTestActivity.class,
+ newDisplay.mId);
+
+ // Tap the EditText on the virtual display.
+ editText = imeTestActivitySession.getActivity().mEditText;
+ tapOnDisplayCenter(newDisplay.mId);
+ editText.getLocationOnScreen(location);
+ tapOnDisplaySync(location[0], location[1], newDisplay.mId);
+
+ final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+ // Verify the activity does not show soft input.
+ notExpectEvent(stream, editorMatcher("onStartInput", editText.getPrivateImeOptions()),
+ TIMEOUT);
+ InputMethodVisibilityVerifier.expectImeInvisible(TIMEOUT);
+ }
+
@Test
public void testImeWindowCanShownWhenActivityMovedToDisplay() throws Exception {
// If config_perDisplayFocusEnabled, the focus will not move even if touching on
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 4e0c05e..d127589 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -111,6 +111,8 @@
import android.util.Log;
import android.util.Size;
+import androidx.test.filters.FlakyTest;
+
import com.android.compatibility.common.util.AppOpsUtils;
import com.android.compatibility.common.util.SystemUtil;
@@ -596,6 +598,7 @@
assertEquals(1, mWmState.countStacks(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD));
}
+ @FlakyTest(bugId = 183538974)
@Test
public void testDisallowMultipleTasksInPinnedStack() throws Exception {
// Launch a test activity so that we have multiple fullscreen tasks
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
index 91db271..e08229e 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
@@ -34,6 +34,7 @@
import static android.server.wm.app.Components.TestStartingWindowKeys.HANDLE_SPLASH_SCREEN_EXIT;
import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_ANIMATION_DURATION;
import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_ANIMATION_START;
+import static android.server.wm.app.Components.TestStartingWindowKeys.ICON_BACKGROUND_COLOR;
import static android.server.wm.app.Components.TestStartingWindowKeys.RECEIVE_SPLASH_SCREEN_EXIT;
import static android.server.wm.app.Components.TestStartingWindowKeys.REPLACE_ICON_EXIT;
import static android.server.wm.app.Components.TestStartingWindowKeys.REQUEST_HANDLE_EXIT_ON_CREATE;
@@ -191,6 +192,8 @@
() -> expectResult == journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
assertEquals(expectResult, journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
assertEquals(expectResult, journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
+ assertEquals(expectResult ? Color.BLUE : Color.TRANSPARENT,
+ journal.extras.getInt(ICON_BACKGROUND_COLOR));
}
@Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
index 324fb67..571fa9d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
@@ -19,6 +19,7 @@
import static android.server.wm.UiDeviceUtils.pressHomeButton;
import static android.server.wm.UiDeviceUtils.pressUnlockButton;
import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+import static android.view.SurfaceControlViewHost.*;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static org.junit.Assert.assertEquals;
@@ -377,4 +378,59 @@
// assert host has focus
assertWindowFocused(mSurfaceView, true);
}
+
+ private static class SurfaceCreatedCallback implements SurfaceHolder.Callback {
+ private final CountDownLatch mSurfaceCreated;
+ SurfaceCreatedCallback(CountDownLatch latch) {
+ mSurfaceCreated = latch;
+ }
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mSurfaceCreated.countDown();
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {}
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
+ }
+
+ @Test
+ public void testCanCopySurfacePackage() throws Throwable {
+ // Create a surface view and wait for its surface to be created.
+ CountDownLatch surfaceCreated = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ final FrameLayout content = new FrameLayout(mActivity);
+ mSurfaceView = new SurfaceView(mActivity);
+ mSurfaceView.setZOrderOnTop(true);
+ content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+ DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, Gravity.LEFT | Gravity.TOP));
+ mActivity.setContentView(content, new ViewGroup.LayoutParams(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT));
+ mSurfaceView.getHolder().addCallback(new SurfaceCreatedCallback(surfaceCreated));
+
+ // Create an embedded view.
+ mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
+ mSurfaceView.getHostToken());
+ mEmbeddedView = new Button(mActivity);
+ mEmbeddedView.setOnClickListener((View v) -> mClicked = true);
+ mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight);
+
+ });
+ surfaceCreated.await();
+
+ // Make a copy of the SurfacePackage and release the original package.
+ SurfacePackage surfacePackage = mVr.getSurfacePackage();
+ SurfacePackage copy = new SurfacePackage(surfacePackage);
+ surfacePackage.release();
+ mSurfaceView.setChildSurfacePackage(copy);
+
+ mInstrumentation.waitForIdleSync();
+ waitUntilEmbeddedViewDrawn();
+
+ // Check if SurfacePackage copy remains valid even though the original package has
+ // been released.
+ CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
+ assertTrue(mClicked);
+ }
}
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 c6778a8..2ca333e 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
@@ -198,7 +198,7 @@
@Test
public void testCloseActivity_BothWallpaper_Translucent() {
testCloseActivityTranslucent(true /*bottomWallpaper*/, true /*topWallpaper*/,
- TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE);
+ TRANSIT_WALLPAPER_INTRA_CLOSE);
}
@Test
@@ -216,7 +216,7 @@
@Test
public void testCloseTask_BothWallpaper_Translucent() {
testCloseTaskTranslucent(true /*bottomWallpaper*/, true /*topWallpaper*/,
- TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE);
+ TRANSIT_WALLPAPER_INTRA_CLOSE);
}
//------------------------------------------------------------------------//
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java
index cd66878..36f9890 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java
@@ -22,10 +22,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import android.content.ComponentCallbacks;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.IBinder;
import android.platform.test.annotations.AppModeFull;
@@ -33,8 +35,13 @@
import android.view.View;
import android.view.WindowManager;
+import androidx.annotation.NonNull;
+
import org.junit.Test;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
/**
* Tests that verify the behavior of window context
*
@@ -99,4 +106,45 @@
windowContext.unbindService(serviceConnection);
}
}
+
+ /**
+ * Verify if the {@link ComponentCallbacks#onConfigurationChanged(Configuration)} callback
+ * is received when the window context configuration changes.
+ */
+ @Test
+ public void testWindowContextRegisterComponentCallbacks() throws Exception {
+ final TestComponentCallbacks callbacks = new TestComponentCallbacks();
+ final WindowManagerState.DisplayContent display = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
+ final Context windowContext = createWindowContext(display.mId);
+ final DisplayMetricsSession displayMetricsSession =
+ createManagedDisplayMetricsSession(display.mId);
+
+ windowContext.registerComponentCallbacks(callbacks);
+
+ callbacks.mLatch = new CountDownLatch(1);
+
+ displayMetricsSession.changeDisplayMetrics(1.2 /* sizeRatio */, 1.1 /* densityRatio */);
+
+ // verify if there is a gicallback from the window context configuration change.
+ assertTrue(callbacks.mLatch.await(4, TimeUnit.SECONDS));
+ Rect bounds = callbacks.mConfiguration.windowConfiguration.getBounds();
+ assertBoundsEquals(displayMetricsSession.getDisplayMetrics(), bounds);
+
+ windowContext.unregisterComponentCallbacks(callbacks);
+ }
+
+ private static class TestComponentCallbacks implements ComponentCallbacks {
+ private Configuration mConfiguration;
+ private CountDownLatch mLatch = new CountDownLatch(1);
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ mConfiguration = newConfig;
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onLowMemory() {}
+ }
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTestBase.java
index 4bd5106..76b34f7 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTestBase.java
@@ -21,6 +21,7 @@
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
@@ -338,6 +339,8 @@
getWindow().getAttributes().layoutInDisplayCutoutMode =
LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
getWindow().setSoftInputMode(SOFT_INPUT_STATE_HIDDEN);
+ getWindow().getDecorView().getWindowInsetsController().setSystemBarsBehavior(
+ BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
setContentView(mView);
mEditor.requestFocus();
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
index 79fe362..6e01a3c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
@@ -94,6 +94,7 @@
public class WindowInsetsControllerTests extends WindowManagerTestBase {
private final static long TIMEOUT = 1000; // milliseconds
+ private final static long TIMEOUT_UPDATING_INPUT_WINDOW = 500; // milliseconds
private final static long TIME_SLICE = 50; // milliseconds
private final static AnimationCallback ANIMATION_CALLBACK = new AnimationCallback();
@@ -233,6 +234,10 @@
tapOnDisplay(rootView.getWidth() / 2f, rootView.getHeight() / 2f);
PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
+ // Wait for status bar invisible from InputDispatcher. Otherwise, the following
+ // dragFromTopToCenter might expand notification shade.
+ SystemClock.sleep(TIMEOUT_UPDATING_INPUT_WINDOW);
+
// Swiping from top of display can show bars.
dragFromTopToCenter(rootView);
PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
@@ -256,9 +261,17 @@
tapOnDisplay(rootView.getWidth() / 2f, rootView.getHeight() / 2f);
PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
+ // Wait for status bar invisible from InputDispatcher. Otherwise, the following
+ // dragFromTopToCenter might expand notification shade.
+ SystemClock.sleep(TIMEOUT_UPDATING_INPUT_WINDOW);
+
// Swiping from top of display can show transient bars, but apps cannot detect that.
dragFromTopToCenter(rootView);
- PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
+ // Make sure status bar stays invisible.
+ for (long time = TIMEOUT; time >= 0; time -= TIME_SLICE) {
+ assertFalse(rootView.getRootWindowInsets().isVisible(types));
+ SystemClock.sleep(TIME_SLICE);
+ }
}
@Test
@@ -395,14 +408,19 @@
rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
rootView.setSystemUiVisibility(targetFlags);
});
- ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+ ANIMATION_CALLBACK.waitForFinishing();
PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
- getInstrumentation().waitForIdleSync();
+ // Wait for status bar invisible from InputDispatcher. Otherwise, the following
+ // dragFromTopToCenter might expand notification shade.
+ SystemClock.sleep(TIMEOUT_UPDATING_INPUT_WINDOW);
// Swiping from top of display can show bars.
+ ANIMATION_CALLBACK.reset();
dragFromTopToCenter(rootView);
- PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
+ ANIMATION_CALLBACK.waitForFinishing();
+ PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types)
+ && rootView.getSystemUiVisibility() != targetFlags);
// Use flags to hide status bar again.
ANIMATION_CALLBACK.reset();
@@ -410,11 +428,17 @@
rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
rootView.setSystemUiVisibility(targetFlags);
});
- ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+ ANIMATION_CALLBACK.waitForFinishing();
PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
+ // Wait for status bar invisible from InputDispatcher. Otherwise, the following
+ // dragFromTopToCenter might expand notification shade.
+ SystemClock.sleep(TIMEOUT_UPDATING_INPUT_WINDOW);
+
// Swiping from top of display can show bars.
+ ANIMATION_CALLBACK.reset();
dragFromTopToCenter(rootView);
+ ANIMATION_CALLBACK.waitForFinishing();
PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(types));
// The swipe action brings down the notification shade which causes subsequent tests to
@@ -441,7 +465,7 @@
rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
rootView.setSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN);
});
- ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+ ANIMATION_CALLBACK.waitForFinishing();
PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
// Clearing the flag can show status bar.
@@ -456,7 +480,7 @@
rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
rootView.setSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN);
});
- ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+ ANIMATION_CALLBACK.waitForFinishing();
PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(types));
// Clearing the flag can show status bar.
@@ -470,7 +494,7 @@
public void testHideOnCreate() throws Exception {
final TestHideOnCreateActivity activity = startActivity(TestHideOnCreateActivity.class);
final View rootView = activity.getWindow().getDecorView();
- ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+ ANIMATION_CALLBACK.waitForFinishing();
PollingCheck.waitFor(TIMEOUT,
() -> !rootView.getRootWindowInsets().isVisible(statusBars())
&& !rootView.getRootWindowInsets().isVisible(navigationBars()));
@@ -484,7 +508,7 @@
MockImeHelper.createManagedMockImeSession(this);
final TestShowOnCreateActivity activity = startActivity(TestShowOnCreateActivity.class);
final View rootView = activity.getWindow().getDecorView();
- ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+ ANIMATION_CALLBACK.waitForFinishing();
PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(ime()));
}
@@ -493,7 +517,7 @@
// Start an activity which hides system bars.
final TestHideOnCreateActivity activity = startActivity(TestHideOnCreateActivity.class);
final View rootView = activity.getWindow().getDecorView();
- ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+ ANIMATION_CALLBACK.waitForFinishing();
PollingCheck.waitFor(TIMEOUT,
() -> !rootView.getRootWindowInsets().isVisible(statusBars())
&& !rootView.getRootWindowInsets().isVisible(navigationBars()));
@@ -524,7 +548,7 @@
public void testWindowInsetsController_availableAfterAddView() throws Exception {
final TestHideOnCreateActivity activity = startActivity(TestHideOnCreateActivity.class);
final View rootView = activity.getWindow().getDecorView();
- ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+ ANIMATION_CALLBACK.waitForFinishing();
PollingCheck.waitFor(TIMEOUT,
() -> !rootView.getRootWindowInsets().isVisible(statusBars())
&& !rootView.getRootWindowInsets().isVisible(navigationBars()));
@@ -572,7 +596,7 @@
rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
rootView.getWindowInsetsController().hide(systemBars());
});
- ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+ ANIMATION_CALLBACK.waitForFinishing();
// ... should only trigger one dispatchApplyWindowInsets
assertEquals(1, dispatchApplyWindowInsetsCount[0]);
@@ -584,7 +608,7 @@
rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
rootView.getWindowInsetsController().show(systemBars());
});
- ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+ ANIMATION_CALLBACK.waitForFinishing();
// ... should only trigger one dispatchApplyWindowInsets
assertEquals(1, dispatchApplyWindowInsetsCount[0]);
@@ -612,7 +636,7 @@
rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
rootView.getWindowInsetsController().show(ime());
});
- ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+ ANIMATION_CALLBACK.waitForFinishing();
// ... should only trigger one dispatchApplyWindowInsets
assertEquals(1, dispatchApplyWindowInsetsCount[0]);
@@ -624,7 +648,7 @@
rootView.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
rootView.getWindowInsetsController().hide(ime());
});
- ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+ ANIMATION_CALLBACK.waitForFinishing();
// ... should only trigger one dispatchApplyWindowInsets
assertEquals(1, dispatchApplyWindowInsetsCount[0]);
@@ -644,7 +668,7 @@
view.setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
view.getWindowInsetsController().hide(types);
});
- ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+ ANIMATION_CALLBACK.waitForFinishing();
PollingCheck.waitFor(TIMEOUT, () -> !view.getRootWindowInsets().isVisible(types));
}
@@ -691,6 +715,8 @@
private static class AnimationCallback extends WindowInsetsAnimation.Callback {
+ private static final long ANIMATION_TIMEOUT = 5000; // milliseconds
+
private boolean mFinished = false;
AnimationCallback() {
@@ -711,10 +737,10 @@
}
}
- void waitForFinishing(long timeout) throws InterruptedException {
+ void waitForFinishing() throws InterruptedException {
synchronized (this) {
if (!mFinished) {
- wait(timeout);
+ wait(ANIMATION_TIMEOUT);
}
}
}
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 d439d54..764e097 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
@@ -1039,6 +1039,10 @@
return mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
+ protected boolean isOperatorTierDevice() {
+ return hasDeviceFeature("com.google.android.tv.operator_tier");
+ }
+
protected void waitAndAssertActivityState(ComponentName activityName,
String state, String message) {
mWmState.waitForActivityState(activityName, state);
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java b/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
index 2e23f5e..ff5a31f 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
@@ -51,7 +51,7 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
-class TestTaskOrganizer extends TaskOrganizer {
+public class TestTaskOrganizer extends TaskOrganizer {
private static final String TAG = TestTaskOrganizer.class.getSimpleName();
public static final int INVALID_TASK_ID = -1;
@@ -79,7 +79,7 @@
WINDOWING_MODE_UNDEFINED
};
- TestTaskOrganizer(Context context) {
+ public TestTaskOrganizer(Context context) {
super();
mContext = context;
}
@@ -158,7 +158,7 @@
mRegistered = true;
}
- void unregisterOrganizerIfNeeded() {
+ public void unregisterOrganizerIfNeeded() {
synchronized (this) {
if (!mRegistered) return;
mRegistered = false;
@@ -180,7 +180,7 @@
}
}
- void putTaskInSplitPrimary(int taskId) {
+ public void putTaskInSplitPrimary(int taskId) {
NestedShellPermission.run(() -> {
synchronized (this) {
registerOrganizerIfNeeded();
@@ -202,7 +202,7 @@
});
}
- void putTaskInSplitSecondary(int taskId) {
+ public void putTaskInSplitSecondary(int taskId) {
NestedShellPermission.run(() -> {
synchronized (this) {
registerOrganizerIfNeeded();
@@ -272,6 +272,14 @@
setTaskBounds(mRootSecondary.getToken(), bounds);
}
+ public Rect getPrimaryTaskBounds() {
+ return mPrimaryBounds;
+ }
+
+ public Rect getSecondaryTaskBounds() {
+ return mSecondaryBounds;
+ }
+
private void setTaskBounds(WindowContainerToken container, Rect bounds) {
synchronized (this) {
NestedShellPermission.run(() -> {
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index 01e4a82..182bb11 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -865,8 +865,12 @@
presentationSpecs.add(new InlinePresentationSpec.Builder(new Size(100, 100),
new Size(400, 100)).setStyle(styles).build());
+ final InlinePresentationSpec tooltipSpec =
+ new InlinePresentationSpec.Builder(new Size(100, 100),
+ new Size(400, 100)).setStyle(styles).build();
final InlineSuggestionsRequest.Builder builder =
new InlineSuggestionsRequest.Builder(presentationSpecs)
+ .setInlineTooltipPresentationSpec(tooltipSpec)
.setMaxSuggestionCount(6);
if (mInlineSuggestionsExtras != null) {
builder.setExtras(mInlineSuggestionsExtras.deepCopy());
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
index 8b04640..acc030b 100644
--- a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
@@ -18,6 +18,7 @@
import android.content.ComponentName
import android.service.textservice.SpellCheckerService
import android.util.Log
+import android.view.textservice.SentenceSuggestionsInfo
import android.view.textservice.SuggestionsInfo
import android.view.textservice.TextInfo
import com.android.cts.mockspellchecker.MockSpellCheckerProto.MockSpellCheckerConfiguration
@@ -54,6 +55,34 @@
override fun onCreate() = withLog("MockSpellCheckerSession.onCreate") {
}
+ override fun onGetSentenceSuggestionsMultiple(
+ textInfos: Array<out TextInfo>?,
+ suggestionsLimit: Int
+ ): Array<SentenceSuggestionsInfo> = withLog(
+ "MockSpellCheckerSession.onGetSuggestionsMultiple " +
+ "${textInfos?.map { it.text }?.joinToString(":")}") {
+ if (textInfos == null) return emptyArray()
+ val configuration = MockSpellCheckerConfiguration.parseFrom(
+ SharedPrefsProvider.get(contentResolver, KEY_CONFIGURATION))
+ if (configuration.matchSentence)
+ return textInfos.map { matchSentenceSuggestion(configuration, it) }.toTypedArray()
+ return super.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit)
+ }
+
+ private fun matchSentenceSuggestion(
+ configuration: MockSpellCheckerConfiguration,
+ textInfo: TextInfo
+ ): SentenceSuggestionsInfo {
+ return configuration.suggestionRulesList.find { it.match == textInfo.text }
+ ?.let {
+ SentenceSuggestionsInfo(
+ arrayOf(suggestionsInfo(it, textInfo.cookie, textInfo.sequence)),
+ intArrayOf(0),
+ intArrayOf(textInfo.text.length))
+ }
+ ?: SentenceSuggestionsInfo(emptyArray(), intArrayOf(), intArrayOf())
+ }
+
override fun onGetSuggestions(
textInfo: TextInfo?,
suggestionsLimit: Int
@@ -69,9 +98,17 @@
}
private fun suggestionsInfo(rule: SuggestionRule): SuggestionsInfo {
+ return suggestionsInfo(rule, 0, 0)
+ }
+
+ private fun suggestionsInfo(
+ rule: SuggestionRule,
+ cookie: Int,
+ sequence: Int
+ ): SuggestionsInfo {
// Only use attrs in supportedAttributes
val attrs = rule.attributes and supportedAttributes
- return SuggestionsInfo(attrs, rule.suggestionsList.toTypedArray())
+ return SuggestionsInfo(attrs, rule.suggestionsList.toTypedArray(), cookie, sequence)
}
private fun emptySuggestionsInfo() = SuggestionsInfo(0, arrayOf())
@@ -82,4 +119,4 @@
fun getId(): String =
ComponentName(PACKAGE, MockSpellChecker::class.java.name).flattenToShortString()
}
-}
+}
\ No newline at end of file
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto
index 58b127f..35574ba 100644
--- a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto
@@ -31,4 +31,5 @@
// Represents a MockSpellChecker configuration.
message MockSpellCheckerConfiguration {
repeated SuggestionRule suggestion_rules = 1;
+ optional bool match_sentence = 2;
};
diff --git a/tests/inputmethod/spellcheckingime/Android.bp b/tests/inputmethod/spellcheckingime/Android.bp
index d486750..a9a08cb 100644
--- a/tests/inputmethod/spellcheckingime/Android.bp
+++ b/tests/inputmethod/spellcheckingime/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
android_test_helper_app {
name: "CtsSpellCheckingIme",
srcs: ["src/**/*.java"],
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
index 470dd15..fe67b1e 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
@@ -18,6 +18,7 @@
import android.app.Instrumentation
import android.app.UiAutomation
import android.content.Context
+import android.os.Looper
import android.provider.Settings
import android.text.style.SuggestionSpan
import android.text.style.SuggestionSpan.FLAG_GRAMMAR_ERROR
@@ -69,7 +70,9 @@
import org.junit.Test
import org.junit.runner.RunWith
import java.util.Locale
+import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
+import kotlin.collections.ArrayList
private val TIMEOUT = TimeUnit.SECONDS.toMillis(5)
@@ -77,7 +80,8 @@
@RunWith(AndroidJUnit4::class)
class SpellCheckerTest : EndToEndImeTestBase() {
- val SPELL_CHECKING_IME_ID = "com.android.cts.spellcheckingime/.SpellCheckingIme"
+ private val SPELL_CHECKING_IME_ID = "com.android.cts.spellcheckingime/.SpellCheckingIme"
+ private val TIMEOUT = TimeUnit.SECONDS.toMillis(5)
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val context: Context = instrumentation.getTargetContext()
@@ -245,11 +249,80 @@
val spellCheckerInfo = tsm.getCurrentSpellCheckerInfo()
assertThat(spellCheckerInfo).isNotNull()
assertThat(spellCheckerInfo!!.getPackageName()).isEqualTo(
- "com.android.cts.mockspellchecker")
+ "com.android.cts.mockspellchecker")
assertThat(spellCheckerInfo!!.getSubtypeCount()).isEqualTo(1)
assertThat(tsm.getEnabledSpellCheckerInfos()!!.size).isAtLeast(1)
assertThat(tsm.getEnabledSpellCheckerInfos()!!.map { it.getPackageName() })
- .contains("com.android.cts.mockspellchecker")
+ .contains("com.android.cts.mockspellchecker")
+ }
+
+ @Test
+ fun newSpellCheckerSession() {
+ val configuration = MockSpellCheckerConfiguration.newBuilder()
+ .addSuggestionRules(
+ MockSpellCheckerProto.SuggestionRule.newBuilder()
+ .setMatch("match")
+ .addSuggestions("suggestion")
+ .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO)
+ ).build()
+ MockSpellCheckerClient.create(context, configuration).use {
+ val tsm = context.getSystemService(TextServicesManager::class.java)
+ assertThat(tsm).isNotNull()
+ val fakeListener = FakeSpellCheckerSessionListener()
+ val fakeExecutor = FakeExecutor()
+ var session: SpellCheckerSession? = tsm?.newSpellCheckerSession(Locale.US, false,
+ RESULT_ATTR_LOOKS_LIKE_TYPO, null, fakeExecutor, fakeListener)
+ assertThat(session).isNotNull()
+ session?.getSentenceSuggestions(arrayOf(TextInfo("match")), 5)
+ waitOnMainUntil({ fakeExecutor.runnables.size == 1 }, TIMEOUT)
+ fakeExecutor.runnables[0].run()
+
+ assertThat(fakeListener.getSentenceSuggestionsResults).hasSize(1)
+ assertThat(fakeListener.getSentenceSuggestionsResults[0]).hasLength(1)
+ val sentenceSuggestionsInfo = fakeListener.getSentenceSuggestionsResults[0]!![0]
+ assertThat(sentenceSuggestionsInfo.suggestionsCount).isEqualTo(1)
+ assertThat(sentenceSuggestionsInfo.getOffsetAt(0)).isEqualTo(0)
+ assertThat(sentenceSuggestionsInfo.getLengthAt(0)).isEqualTo("match".length)
+ val suggestionsInfo = sentenceSuggestionsInfo.getSuggestionsInfoAt(0)
+ assertThat(suggestionsInfo.suggestionsCount).isEqualTo(1)
+ assertThat(suggestionsInfo.getSuggestionAt(0)).isEqualTo("suggestion")
+
+ assertThat(fakeListener.getSentenceSuggestionsResults).hasSize(1)
+ assertThat(fakeListener.getSentenceSuggestionsCallingThreads).hasSize(1)
+ assertThat(fakeListener.getSentenceSuggestionsCallingThreads[0])
+ .isEqualTo(Thread.currentThread())
+ }
+ }
+
+ @Test
+ fun newSpellCheckerSession_implicitExecutor() {
+ val configuration = MockSpellCheckerConfiguration.newBuilder()
+ .addSuggestionRules(
+ MockSpellCheckerProto.SuggestionRule.newBuilder()
+ .setMatch("match")
+ .addSuggestions("suggestion")
+ .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO)
+ ).build()
+ MockSpellCheckerClient.create(context, configuration).use {
+ val tsm = context.getSystemService(TextServicesManager::class.java)
+ assertThat(tsm).isNotNull()
+ val fakeListener = FakeSpellCheckerSessionListener()
+ var session: SpellCheckerSession? = null
+ runOnMainSync {
+ session = tsm?.newSpellCheckerSession(null /* bundle */, Locale.US,
+ fakeListener, false /* referToSpellCheckerLanguageSettings */)
+ }
+ assertThat(session).isNotNull()
+ session?.getSentenceSuggestions(arrayOf(TextInfo("match")), 5)
+ waitOnMainUntil({
+ fakeListener.getSentenceSuggestionsCallingThreads.size > 0
+ }, TIMEOUT)
+ runOnMainSync {
+ assertThat(fakeListener.getSentenceSuggestionsCallingThreads).hasSize(1)
+ assertThat(fakeListener.getSentenceSuggestionsCallingThreads[0])
+ .isEqualTo(Looper.getMainLooper().thread)
+ }
+ }
}
@Test
@@ -301,6 +374,75 @@
}
}
+ @Test
+ fun trailingPunctuation() {
+ // Set up a rule that matches the sentence "match?" and marks it as grammar error.
+ val configuration = MockSpellCheckerConfiguration.newBuilder()
+ .setMatchSentence(true)
+ .addSuggestionRules(
+ MockSpellCheckerProto.SuggestionRule.newBuilder()
+ .setMatch("match?")
+ .addSuggestions("suggestion.")
+ .setAttributes(RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR)
+ ).build()
+ MockImeSession.create(context).use { session ->
+ MockSpellCheckerClient.create(context, configuration).use { client ->
+ val (_, editText) = startTestActivity()
+ CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+ waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+ InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+ session.callCommitText("match", 1)
+ // The trailing punctuation "?" is also sent in the next spell check, and the
+ // sentence "match?" will be marked as FLAG_GRAMMAR_ERROR according to the
+ // configuration.
+ session.callCommitText("?", 1)
+ waitOnMainUntil({
+ findSuggestionSpanWithFlags(editText, FLAG_GRAMMAR_ERROR) != null
+ }, TIMEOUT)
+ }
+ }
+ }
+
+ @Test
+ fun respectSentenceBoundary() {
+ // Set up two rules:
+ // - Matches the sentence "Preceding text?" and marks it as grammar error.
+ // - Matches the sentence "match?" and marks it as misspelled.
+ val configuration = MockSpellCheckerConfiguration.newBuilder()
+ .setMatchSentence(true)
+ .addSuggestionRules(
+ MockSpellCheckerProto.SuggestionRule.newBuilder()
+ .setMatch("Preceding text?")
+ .addSuggestions("suggestion.")
+ .setAttributes(RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR)
+ ).addSuggestionRules(
+ MockSpellCheckerProto.SuggestionRule.newBuilder()
+ .setMatch("match?")
+ .addSuggestions("suggestion.")
+ .setAttributes(RESULT_ATTR_LOOKS_LIKE_TYPO)
+ ).build()
+ MockImeSession.create(context).use { session ->
+ MockSpellCheckerClient.create(context, configuration).use { client ->
+ val (_, editText) = startTestActivity()
+ CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText)
+ waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+ InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+ session.callCommitText("Preceding text", 1)
+ session.callCommitText("?", 1)
+ waitOnMainUntil({
+ findSuggestionSpanWithFlags(editText, FLAG_GRAMMAR_ERROR) != null
+ }, TIMEOUT)
+ // The next spell check only contains the text after "Preceding text?". According
+ // to our configuration, the sentence "match?" will be marked as FLAG_MISSPELLED.
+ session.callCommitText("match", 1)
+ session.callCommitText("?", 1)
+ waitOnMainUntil({
+ findSuggestionSpanWithFlags(editText, FLAG_MISSPELLED) != null
+ }, TIMEOUT)
+ }
+ }
+ }
+
private fun findSuggestionSpanWithFlags(editText: EditText, flags: Int): SuggestionSpan? =
getSuggestionSpans(editText).find { (it.flags and flags) == flags }
@@ -376,6 +518,7 @@
private class FakeSpellCheckerSessionListener :
SpellCheckerSession.SpellCheckerSessionListener {
val getSentenceSuggestionsResults = ArrayList<Array<SentenceSuggestionsInfo>?>()
+ val getSentenceSuggestionsCallingThreads = ArrayList<Thread>()
override fun onGetSuggestions(results: Array<SuggestionsInfo>?) {
fail("Not expected")
@@ -383,6 +526,17 @@
override fun onGetSentenceSuggestions(results: Array<SentenceSuggestionsInfo>?) {
getSentenceSuggestionsResults.add(results)
+ getSentenceSuggestionsCallingThreads.add(Thread.currentThread())
+ }
+ }
+
+ private class FakeExecutor : Executor {
+ @get:Synchronized
+ val runnables = ArrayList<Runnable>()
+
+ @Synchronized
+ override fun execute(r: Runnable) {
+ runnables.add(r)
}
}
}
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
index e00fdf7..1826715 100644
--- a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
+++ b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
@@ -992,13 +992,7 @@
Executors.newSingleThreadExecutor(), capture);
mManager.requestFlush(GPS_PROVIDER, capture, 1);
- // Temporarily passing with warning while waiting for vendor's fix in b/175833551
- // TODO: revert to assert when b/175833551 is fixed
- try {
- capture.getNextFlush(TIMEOUT_MS);
- } catch (InterruptedException e) {
- Log.w(TAG, "GNSS flush failed.", e);
- }
+ assertThat(capture.getNextFlush(TIMEOUT_MS)).isEqualTo(1);
}
}
diff --git a/tests/location/location_privileged/src/android/location/cts/privileged/GnssCapabilitiesTest.java b/tests/location/location_privileged/src/android/location/cts/privileged/GnssCapabilitiesTest.java
new file mode 100644
index 0000000..467d63d
--- /dev/null
+++ b/tests/location/location_privileged/src/android/location/cts/privileged/GnssCapabilitiesTest.java
@@ -0,0 +1,46 @@
+package android.location.cts.privileged;
+
+import static org.junit.Assert.assertEquals;
+
+import android.location.GnssCapabilities;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of {@link GnssCapabilities}. This includes writing and reading
+ * from parcel, and verifying setters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssCapabilitiesTest {
+
+ @Test
+ public void testGetValues() {
+ GnssCapabilities gnssCapabilities = getTestGnssCapabilities();
+ verifyTestValues(gnssCapabilities);
+ }
+
+ @Test
+ public void testWriteToParcel() {
+ GnssCapabilities gnssCapabilities = getTestGnssCapabilities();
+ Parcel parcel = Parcel.obtain();
+ gnssCapabilities.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ GnssCapabilities newGnssCapabilities = GnssCapabilities.CREATOR.createFromParcel(parcel);
+ verifyTestValues(newGnssCapabilities);
+ parcel.recycle();
+ }
+
+ private static GnssCapabilities getTestGnssCapabilities() {
+ GnssCapabilities.Builder builder = new GnssCapabilities.Builder();
+ builder.setHasMeasurementCorrectionsForDriving(true);
+ return builder.build();
+ }
+
+ private static void verifyTestValues(GnssCapabilities gnssCapabilities) {
+ assertEquals(true, gnssCapabilities.hasMeasurementCorrectionsForDriving());
+ }
+}
\ No newline at end of file
diff --git a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
index 5d780f0..07e0914 100644
--- a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
+++ b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
@@ -209,7 +209,8 @@
AVCProfileConstrainedBaseline, AVCProfileConstrainedHigh});
mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_HEVC,
new int[]{HEVCProfileMain, HEVCProfileMain10, HEVCProfileMainStill,
- HEVCProfileMain10HDR10, HEVCProfileMain10HDR10Plus});
+ // TODO: test HDR profiles once they are supported by MediaMuxer
+ /* HEVCProfileMain10HDR10, HEVCProfileMain10HDR10Plus */});
mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_H263,
new int[]{H263ProfileBaseline, H263ProfileH320Coding,
H263ProfileBackwardCompatible, H263ProfileISWV2, H263ProfileISWV3,
@@ -229,8 +230,9 @@
mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_VP8, new int[]{VP8ProfileMain});
mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_VP9, new int[]{VP9Profile0, VP9Profile1});
mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_AV1,
- new int[]{AV1ProfileMain8, AV1ProfileMain10, AV1ProfileMain10HDR10,
- AV1ProfileMain10HDR10Plus});
+ new int[]{AV1ProfileMain8, AV1ProfileMain10,
+ // TODO: test HDR profiles once they are supported by MediaMuxer
+ /* AV1ProfileMain10HDR10, AV1ProfileMain10HDR10Plus */});
mProfileMap.put(MediaFormat.MIMETYPE_AUDIO_AAC,
new int[]{AACObjectMain, AACObjectLC, AACObjectSSR, AACObjectLTP, AACObjectHE,
AACObjectScalable, AACObjectERLC, AACObjectERScalable, AACObjectLD,
diff --git a/tests/mediapc/Android.bp b/tests/mediapc/Android.bp
index 5230413..dcfdb23 100644
--- a/tests/mediapc/Android.bp
+++ b/tests/mediapc/Android.bp
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
android_test {
name: "CtsMediaPerformanceClassTestCases",
defaults: ["cts_defaults"],
diff --git a/tests/musicrecognition/Android.bp b/tests/musicrecognition/Android.bp
index 39b8826..1b7298a 100644
--- a/tests/musicrecognition/Android.bp
+++ b/tests/musicrecognition/Android.bp
@@ -25,7 +25,6 @@
"ctstestrunner-axt",
"truth-prebuilt",
],
- sdk_version: "system_current",
srcs: ["src/**/*.java"],
// Tag this module as a cts test artifact
test_suites: [
diff --git a/tests/musicrecognition/AndroidManifest.xml b/tests/musicrecognition/AndroidManifest.xml
index c4b6df2..7734a9b 100644
--- a/tests/musicrecognition/AndroidManifest.xml
+++ b/tests/musicrecognition/AndroidManifest.xml
@@ -21,6 +21,16 @@
<uses-permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION" />
+ <!-- Attribution for MusicRecognitionManagerService. -->
+ <attribution android:tag="MusicRecognitionManagerService"
+ android:label="@string/music_recognition_manager_service"/>
+
+ <!-- Attribution for CTS MusicRecognitionService. In this test, music recognition
+ manager and service are in the same process. Otherwise this tag needs to exist
+ in a separate manifest file (in the app implementing MusicRecognitionService). -->
+ <attribution android:tag="CtsMusicRecognitionAttributionTag"
+ android:label="@string/cts_music_recognition_service"/>
+
<application>
<uses-library android:name="android.test.runner"/>
diff --git a/tests/musicrecognition/res/values/strings.xml b/tests/musicrecognition/res/values/strings.xml
new file mode 100644
index 0000000..44f6432
--- /dev/null
+++ b/tests/musicrecognition/res/values/strings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" xmlns:tools="http://schemas.android.com/tools">
+ <!-- Attribution for MusicRecognitionManagerService. [CHAR LIMIT=NONE]-->
+ <string name="music_recognition_manager_service">Music Recognition Manager Service</string>
+ <!-- Attribution for CtsMusicRecognitionService. [CHAR LIMIT=NONE]-->
+ <string name="cts_music_recognition_service">CTS Music Recognition Service</string>
+</resources>
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
index f8ebaac..4813005 100644
--- a/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/CtsMusicRecognitionService.java
@@ -27,6 +27,7 @@
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.google.common.io.ByteStreams;
@@ -63,6 +64,11 @@
}
}
+ @Override
+ public @Nullable String getAttributionTag() {
+ return "CtsMusicRecognitionAttributionTag";
+ }
+
private byte[] readStream(ParcelFileDescriptor stream) {
ParcelFileDescriptor.AutoCloseInputStream fis =
new ParcelFileDescriptor.AutoCloseInputStream(stream);
diff --git a/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
index 9a9ed7b..6c92f79 100644
--- a/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
+++ b/tests/musicrecognition/src/android/musicrecognition/cts/MusicRecognitionManagerTest.java
@@ -37,6 +37,8 @@
import static org.mockito.Mockito.verify;
import android.app.AppOpsManager;
+import android.app.AppOpsManager.OnOpStartedListener;
+
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioFormat;
@@ -76,7 +78,7 @@
public class MusicRecognitionManagerTest {
private static final String TAG = MusicRecognitionManagerTest.class.getSimpleName();
private static final long VERIFY_TIMEOUT_MS = 40_000;
- private static final long VERIFY_APPOP_CHANGE_TIMEOUT_MS = 2000;
+ private static final long VERIFY_APPOP_CHANGE_TIMEOUT_MS = 10000;
@Rule public TestName mTestName = new TestName();
@Rule
@@ -187,16 +189,26 @@
final String packageName = CtsMusicRecognitionService.SERVICE_PACKAGE;
final int uid = Process.myUid();
- final AppOpsManager appOpsManager = getInstrumentation().getContext()
- .getSystemService(AppOpsManager.class);
+
+ final Context context = getInstrumentation().getContext();
+
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
final AppOpsManager.OnOpActiveChangedListener listener = mock(
AppOpsManager.OnOpActiveChangedListener.class);
+
// Assert the app op is not started
assertFalse(appOpsManager.isOpActive(AppOpsManager.OPSTR_RECORD_AUDIO, uid, packageName));
// Start watching for record audio op
appOpsManager.startWatchingActive(new String[] { AppOpsManager.OPSTR_RECORD_AUDIO },
- getInstrumentation().getContext().getMainExecutor(), listener);
+ context.getMainExecutor(), listener);
+
+ // Started listener used just for verifying attribution tag.
+ final AppOpsManager.OnOpStartedListener startedListener = mock(
+ AppOpsManager.OnOpStartedListener.class);
+
+ appOpsManager.startWatchingStarted(new int[] { AppOpsManager.OP_RECORD_AUDIO },
+ startedListener);
// Invoke API
RecognitionRequest request = invokeMusicRecognitionApi();
@@ -206,6 +218,11 @@
.only()).onOpActiveChanged(eq(AppOpsManager.OPSTR_RECORD_AUDIO),
eq(uid), eq(packageName), eq(true));
+ String expectedAttributionTag = "CtsMusicRecognitionAttributionTag";
+ verify(startedListener, timeout(VERIFY_APPOP_CHANGE_TIMEOUT_MS)
+ .only()).onOpStarted(eq(AppOpsManager.OP_RECORD_AUDIO),
+ eq(uid), eq(packageName), eq(expectedAttributionTag), anyInt(), anyInt());
+
// Wait for streaming to finish.
reset(listener);
try {
@@ -234,6 +251,8 @@
private RecognitionRequest invokeMusicRecognitionApi() {
+ Log.d(TAG, "Invoking service.");
+
AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.MIC, 16_000,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 256_000);
@@ -251,6 +270,7 @@
request,
MoreExecutors.directExecutor(),
mCallback);
+ Log.d(TAG, "Invoking service done.");
return request;
}
diff --git a/tests/sensor/sensorratepermission/AndroidTest.xml b/tests/sensor/sensorratepermission/AndroidTest.xml
index 1de4288..63878b5 100644
--- a/tests/sensor/sensorratepermission/AndroidTest.xml
+++ b/tests/sensor/sensorratepermission/AndroidTest.xml
@@ -25,9 +25,6 @@
<option name="cleanup-apks" value="true"/>
<option name="test-file-name" value="CtsSensorRatePermissionReturnedRateInfo.apk"/>
- <option name="test-file-name" value="CtsSensorRatePermissionEventConnectionResampling.apk"/>
- <!-- This APK is a helper app to test the resampling of SensorEventConnections -->
- <option name="test-file-name" value="CtsSensorRatePermissionMicToggleOffAPI30.apk"/>
<option name="test-file-name" value="CtsSensorRatePermissionDirectReportAPI30.apk"/>
<option name="test-file-name" value="CtsSensorRatePermissionDirectReportAPI31.apk"/>
<option name="test-file-name" value="CtsSensorRatePermissionEventConnectionAPI30.apk"/>
@@ -48,11 +45,6 @@
</test>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="android.sensorratepermission.cts.resampling"/>
- <option name="class" value="android.sensorratepermission.cts.resampling.ResamplingTest"/>
- </test>
-
- <test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="android.sensorratepermission.cts.directreportapi30"/>
<option name="class" value="android.sensorratepermission.cts.directreportapi30.DirectReportAPI30Test"/>
</test>
diff --git a/tests/sensor/sensorratepermission/DebuggableAPI31/src/android/sensorratepermission/cts/debuggableapi31/DebuggableAPI31Test.java b/tests/sensor/sensorratepermission/DebuggableAPI31/src/android/sensorratepermission/cts/debuggableapi31/DebuggableAPI31Test.java
index 2f92157..e501f0c 100644
--- a/tests/sensor/sensorratepermission/DebuggableAPI31/src/android/sensorratepermission/cts/debuggableapi31/DebuggableAPI31Test.java
+++ b/tests/sensor/sensorratepermission/DebuggableAPI31/src/android/sensorratepermission/cts/debuggableapi31/DebuggableAPI31Test.java
@@ -107,6 +107,7 @@
| HardwareBuffer.USAGE_SENSOR_DIRECT_DATA);
SensorDirectChannel channel = mSensorManager.createDirectChannel(hardwareBuffer);
channel.configure(s, rateLevel);
+ hardwareBuffer.close();
fail("Should have thrown a SecurityException");
} catch (SecurityException e) {
// Expected
diff --git a/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java b/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java
index b97a2cc..f294fc2 100644
--- a/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java
+++ b/tests/sensor/sensorratepermission/DirectReportAPI30/src/android/sensorratepermission/cts/directreportapi30/DirectReportAPI30Test.java
@@ -171,6 +171,7 @@
mDirectReportTestHelper.readEventsFromHardwareBuffer(token,
hardwareBuffer, sensorEventCount);
channel.close();
+ hardwareBuffer.close();
// Check the sampling rates when the mic toggle were on and off
double rateWhenMicToggleOn =
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/Android.bp b/tests/sensor/sensorratepermission/EventConnectionResampling/Android.bp
deleted file mode 100644
index 80808a1..0000000
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/Android.bp
+++ /dev/null
@@ -1,36 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
- name: "CtsSensorRatePermissionEventConnectionResampling",
- defaults: ["cts_defaults"],
- sdk_version: "current",
- srcs: [
- "src/**/*.java",
- ],
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "vts10",
- "general-tests",
- ],
- // include both the 32 and 64 bit versions
- compile_multilib: "both",
-
- static_libs: [
- "ctstestrunner-axt",
- "compatibility-device-util-axt",
- "truth-prebuilt",
- "androidx.annotation_annotation",
- "cts-sensors-tests",
- ],
- jni_libs: [
- "libcts-sensors-ndk-jni",
- ],
- libs: [
- "android.test.runner.stubs",
- "android.test.base.stubs",
- ],
- stl: "c++_shared",
-}
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/app/AndroidManifest.xml b/tests/sensor/sensorratepermission/EventConnectionResampling/app/AndroidManifest.xml
deleted file mode 100644
index c43bcc5..0000000
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/app/AndroidManifest.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT 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.sensorratepermission.cts.mictoggleoffapi30">
-
- <uses-permission android:name="android.permission.WAKE_LOCK"/>
-
- <!--
- - Targets an SDK < 31 so that it can have high sampling rate.
- - Targets SDK 30 so that the service can still be run while the app is in background
- -->
- <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30"/>
-
- <application
- android:allowBackup="true"
- android:label="AppWithSamplingRatePermission"
- android:supportsRtl="true">
- <service
- android:name=".MainService"
- android:exported="true">
- <intent-filter>
- <category android:name="android.intent.category.LAUNCHER"/>
- <action android:name="android.intent.action.MAIN"/>
- </intent-filter>
- </service>
- </application>
-
-</manifest>
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/app/src/android/sensorratepermission/cts/mictoggleoffapi30/MainService.java b/tests/sensor/sensorratepermission/EventConnectionResampling/app/src/android/sensorratepermission/cts/mictoggleoffapi30/MainService.java
deleted file mode 100644
index 9feafea..0000000
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/app/src/android/sensorratepermission/cts/mictoggleoffapi30/MainService.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.sensorratepermission.cts.mictoggleoffapi30;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.hardware.Sensor;
-import android.hardware.SensorManager;
-import android.hardware.cts.helpers.SensorRatePermissionEventConnectionTestHelper;
-import android.hardware.cts.helpers.TestSensorEnvironment;
-import android.os.IBinder;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper app to test the cases where two apps register listeners at the same time.
- * It targets API 30 and therefore can have high sampling rates.
- */
-public class MainService extends Service {
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- try {
- Context context = this.getApplicationContext();
- SensorManager sensorManager = context.getSystemService(SensorManager.class);
- Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
- if (sensor != null) {
- TestSensorEnvironment testEnvironment = new TestSensorEnvironment(
- context,
- sensor,
- 3000 /* samplingPeriodUs */,
- (int) TimeUnit.SECONDS.toMicros(5));
- SensorRatePermissionEventConnectionTestHelper eventConnectionTestHelper =
- new SensorRatePermissionEventConnectionTestHelper(testEnvironment);
- eventConnectionTestHelper.getSensorEvents(true, 1024 /* numOfEvents */);
- }
- } catch (InterruptedException e) {
-
- }
- stopForeground(true);
- stopSelf();
- return START_STICKY;
- }
-}
\ No newline at end of file
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java b/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java
deleted file mode 100644
index 39a5c7c..0000000
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/src/android/sensorratepermission/cts/resampling/ResamplingTest.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.sensorratepermission.cts.resampling;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.hardware.Sensor;
-import android.hardware.SensorManager;
-import android.hardware.cts.SensorTestCase;
-import android.hardware.cts.helpers.SensorCtsHelper;
-import android.hardware.cts.helpers.SensorRatePermissionEventConnectionTestHelper;
-import android.hardware.cts.helpers.SensorStats;
-import android.hardware.cts.helpers.TestSensorEnvironment;
-import android.hardware.cts.helpers.TestSensorEvent;
-import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
-import android.hardware.cts.helpers.sensorverification.EventGapVerification;
-import android.hardware.cts.helpers.sensorverification.EventOrderingVerification;
-import android.hardware.cts.helpers.sensorverification.EventTimestampSynchronizationVerification;
-import android.hardware.cts.helpers.sensorverification.InitialValueVerification;
-import android.hardware.cts.helpers.sensorverification.JitterVerification;
-import android.hardware.cts.helpers.sensorverification.MagnitudeVerification;
-import android.hardware.cts.helpers.sensorverification.MeanVerification;
-import android.hardware.cts.helpers.sensorverification.StandardDeviationVerification;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Assert;
-
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-
-/**
- * Test the resampler in SensorEventConnection class.
- *
- * The resampler is to prevent the attack where an app without permission might get a sampling
- * rate higher than our capped rate, because at the time it registers a listener, there is another
- * app with the permission and requests a higher sampling rate.
- *
- * Test cases: Two apps register sensor listeners at the same time.
- * - An app targets API 31 w/o HIGH_SAMPLING_RATE_SENSORS, hence being capped.
- * - The other app targets API 30, hence not being capped
- *
- * Expected behaviors:
- * - The one that should be capped is capped
- * - The default verifications of returned sensor events meet the expectations as in other sensor
- * tests
- */
-public class ResamplingTest extends SensorTestCase {
- private static final int NUM_EVENTS_COUNT = 1024;
- private static SensorRatePermissionEventConnectionTestHelper mEventConnectionTestHelper;
- private static Context mContext;
- private static TestSensorEnvironment mTestEnvironment;
-
- @Override
- protected void setUp() throws Exception {
- mContext = InstrumentationRegistry.getInstrumentation().getContext();
- SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
- Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
- if (sensor == null) {
- return;
- }
- mTestEnvironment = new TestSensorEnvironment(
- mContext,
- sensor,
- SensorManager.SENSOR_DELAY_FASTEST,
- (int) TimeUnit.SECONDS.toMicros(5));
- mEventConnectionTestHelper = new SensorRatePermissionEventConnectionTestHelper(
- mTestEnvironment);
- }
-
- public void testResamplingEventConnections() throws Exception {
- if (mTestEnvironment == null || mEventConnectionTestHelper == null) {
- return;
- }
- // Start an app that registers a listener with high sampling rate
- Intent intent = new Intent();
- intent.setComponent(new ComponentName(
- "android.sensorratepermission.cts.mictoggleoffapi30",
- "android.sensorratepermission.cts.mictoggleoffapi30.MainService"));
- mContext.startForegroundService(intent);
-
- // At the same time, register a listener and obtain its sampling rate.
- List<TestSensorEvent> events = mEventConnectionTestHelper.getSensorEvents(
- true,
- NUM_EVENTS_COUNT);
- double obtainedRate = SensorRatePermissionEventConnectionTestHelper.computeAvgRate(events,
- Long.MIN_VALUE, Long.MAX_VALUE);
- Assert.assertTrue(mEventConnectionTestHelper.errorWhenExceedCappedRate(),
- obtainedRate
- <= SensorRatePermissionEventConnectionTestHelper.CAPPED_SAMPLE_RATE_HZ);
- }
-
- public void testSensorDefaultVerifications() throws Exception {
- if (mTestEnvironment == null) {
- return;
- }
- // Sleep to avoid pollution of sensor data from other tests causing default
- // verification failures.
- SensorCtsHelper.sleep(3, TimeUnit.SECONDS);
- TestSensorOperation op = TestSensorOperation.createOperation(
- mTestEnvironment,
- NUM_EVENTS_COUNT);
- op.addVerification(StandardDeviationVerification.getDefault(mTestEnvironment));
- op.addVerification(EventGapVerification.getDefault(mTestEnvironment));
- op.addVerification(EventOrderingVerification.getDefault(mTestEnvironment));
- op.addVerification(JitterVerification.getDefault(mTestEnvironment));
- op.addVerification(MagnitudeVerification.getDefault(mTestEnvironment));
- op.addVerification(MeanVerification.getDefault(mTestEnvironment));
- op.addVerification(EventTimestampSynchronizationVerification.getDefault(mTestEnvironment));
- op.addVerification(InitialValueVerification.getDefault(mTestEnvironment));
- try {
- // Start an app that registers a listener with high sampling rate
- Intent intent = new Intent();
- intent.setComponent(new ComponentName(
- "android.sensorratepermission.cts.mictoggleoffapi30",
- "android.sensorratepermission.cts.mictoggleoffapi30.MainService"));
- mContext.startForegroundService(intent);
- op.execute(getCurrentTestNode());
- } finally {
- SensorStats stats = op.getStats();
- String fileName = String.format(
- "single_%s_%s.txt",
- SensorStats.getSanitizedSensorName(mTestEnvironment.getSensor()),
- mTestEnvironment.getFrequencyString());
- stats.logToFile(mTestEnvironment.getContext(), fileName);
- }
- }
-}
\ No newline at end of file
diff --git a/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionDirectReportTestHelper.java b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionDirectReportTestHelper.java
index e0289fe..d39c5a5 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionDirectReportTestHelper.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionDirectReportTestHelper.java
@@ -53,8 +53,7 @@
Sensor.TYPE_MAGNETIC_FIELD,
Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED
);
- // Use same parameters like in all other tests in this sensor module
- public static final int TEST_RUN_TIME_PERIOD_MILLISEC = 5000; // 1024 * 5 ms
+ public static final int TEST_RUN_TIME_PERIOD_MILLISEC = 1000;
public static final int SENSORS_EVENT_SIZE = 104;
static {
@@ -155,7 +154,7 @@
*/
public List<SensorDirectReportTest.DirectReportSensorEvent> getSensorEvents(int rateLevel)
throws InterruptedException {
- int sensorEventCount = 10240; // 800 Hz * 5s + extra
+ int sensorEventCount = 2000; // 800 Hz * 2.2 * 1s + extra
int sharedMemorySize = sensorEventCount * SENSORS_EVENT_SIZE;
HardwareBuffer hardwareBuffer = HardwareBuffer.create(
sharedMemorySize, 1, HardwareBuffer.BLOB, 1,
@@ -169,6 +168,7 @@
List<SensorDirectReportTest.DirectReportSensorEvent> events =
readEventsFromHardwareBuffer(token, hardwareBuffer, sensorEventCount);
channel.close();
+ hardwareBuffer.close();
return events;
}
diff --git a/tests/sensor/sensorratepermission/EventConnectionResampling/app/Android.bp b/tests/smartspace/Android.bp
similarity index 70%
rename from tests/sensor/sensorratepermission/EventConnectionResampling/app/Android.bp
rename to tests/smartspace/Android.bp
index 8a4fd86..a72c6cd 100644
--- a/tests/sensor/sensorratepermission/EventConnectionResampling/app/Android.bp
+++ b/tests/smartspace/Android.bp
@@ -1,5 +1,4 @@
-//
-// Copyright (C) 2020 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.
@@ -12,28 +11,25 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-//
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test_helper_app {
- name: "CtsSensorRatePermissionMicToggleOffAPI30",
- defaults: ["cts_support_defaults"],
+android_test {
+ name: "CtsSmartspaceServiceTestCases",
+ defaults: ["cts_defaults"],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "truth-prebuilt",
+ ],
srcs: ["src/**/*.java"],
// Tag this module as a cts test artifact
test_suites: [
"cts",
- "vts10",
"general-tests",
],
- // include both the 32 and 64 bit versions
- compile_multilib: "both",
-
- static_libs: [
- "androidx.legacy_legacy-support-v4",
- "cts-sensors-tests",
- ],
sdk_version: "test_current",
}
diff --git a/tests/smartspace/AndroidManifest.xml b/tests/smartspace/AndroidManifest.xml
new file mode 100644
index 0000000..4a0658d
--- /dev/null
+++ b/tests/smartspace/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.smartspace.cts"
+ android:targetSandboxVersion="2">
+
+ <uses-permission android:name="android.permission.MANAGE_SMARTSPACE"/>
+
+ <application>
+ <service android:name=".CtsSmartspaceService"
+ android:exported="true"
+ android:label="CtsDummySmartspaceService">
+ <intent-filter>
+ <!-- This constant must match SmartspaceService.SERVICE_INTERFACE -->
+ <action android:name="android.service.smartspace.SmartspaceService"/>
+ </intent-filter>
+ </service>
+
+ <!-- TODO(b/111701043): Update with required permissions -->
+ <uses-library android:name="android.test.runner"/>
+
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for the App Prediction Framework APIs."
+ android:targetPackage="android.smartspace.cts">
+ </instrumentation>
+
+</manifest>
diff --git a/tests/smartspace/AndroidTest.xml b/tests/smartspace/AndroidTest.xml
new file mode 100644
index 0000000..ad3aeeb
--- /dev/null
+++ b/tests/smartspace/AndroidTest.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.
+-->
+<configuration description="Config for Smartspace CTS tests.">
+ <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"/>
+ <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="CtsSmartspaceServiceTestCases.apk"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.smartspace.cts"/>
+ <!-- 20x default timeout of 600sec -->
+ <option name="shell-timeout" value="12000000"/>
+ </test>
+
+</configuration>
diff --git a/tests/smartspace/OWNERS b/tests/smartspace/OWNERS
new file mode 100644
index 0000000..4a61d75
--- /dev/null
+++ b/tests/smartspace/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 487497
+srazdan@google.com
+alexmang@google.com
diff --git a/tests/smartspace/TEST_MAPPING b/tests/smartspace/TEST_MAPPING
new file mode 100644
index 0000000..58b65d1
--- /dev/null
+++ b/tests/smartspace/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsSmartspaceServiceTestCases"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/smartspace/src/android/smartspace/cts/CtsSmartspaceService.java b/tests/smartspace/src/android/smartspace/cts/CtsSmartspaceService.java
new file mode 100644
index 0000000..a285511
--- /dev/null
+++ b/tests/smartspace/src/android/smartspace/cts/CtsSmartspaceService.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.smartspace.cts;
+
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.os.Process;
+import android.service.smartspace.SmartspaceService;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+public class CtsSmartspaceService extends SmartspaceService {
+
+ private static final boolean DEBUG = true;
+ public static final String EXTRA_REPORTER = "extra_reporter";
+ public static final String MY_PACKAGE = "android.smartspace.cts";
+ public static final String SERVICE_NAME = MY_PACKAGE + "/."
+ + CtsSmartspaceService.class.getSimpleName();
+ private static final String TAG = CtsSmartspaceService.class.getSimpleName();
+
+ private static Watcher sWatcher;
+
+ private final ArrayMap<SmartspaceSessionId, List<SmartspaceTarget>> targets = new ArrayMap<>();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+// Log.d(TAG, "onCreate mSessionCallbacks: " + mSessionCallbacks);
+ if (DEBUG) Log.d(TAG, "onCreate");
+ }
+
+ @Override
+ public void onCreateSmartspaceSession(SmartspaceConfig config, SmartspaceSessionId sessionId) {
+// Log.d(TAG, "onCreateSmartspaceSession mSessionCallbacks: " + mSessionCallbacks);
+ if (DEBUG) Log.d(TAG, "onCreateSmartspaceSession");
+
+ if (sWatcher.verifier != null) {
+ Log.e(TAG, "onCreateSmartspaceSession, trying to set verifier when it already exists");
+ }
+ targets.put(sessionId, new ArrayList<>());
+ sWatcher.verifier = Mockito.mock(CtsSmartspaceService.class);
+ sWatcher.created.countDown();
+ }
+
+ @Override
+ public void notifySmartspaceEvent(SmartspaceSessionId sessionId, SmartspaceTargetEvent event) {
+// Log.d(TAG, "notifySmartspaceEvent mSessionCallbacks: " + mSessionCallbacks);
+ if (DEBUG){
+ Log.d(TAG, "notifySmartspaceEvent sessionId=" + sessionId + ", event=" + event.toString());
+ }
+ if(event.getSmartspaceTarget() != null) {
+ targets.get(sessionId).add(event.getSmartspaceTarget());
+ }
+ sWatcher.verifier.notifySmartspaceEvent(sessionId, event);
+ }
+
+ @Override
+ public void onRequestSmartspaceUpdate(SmartspaceSessionId sessionId) {
+// Log.d(TAG, "onRequestSmartspaceUpdate mSessionCallbacks: " + mSessionCallbacks);
+ if (DEBUG){
+ Log.d(TAG, "onRequestSmartspaceUpdate sessionId=" + sessionId);
+ }
+ List<SmartspaceTarget> returnList = targets.get(sessionId);
+ if(returnList == null) {
+ returnList = new ArrayList<>();
+ }
+ updateSmartspaceTargets(sessionId, returnList);
+ sWatcher.verifier.onRequestSmartspaceUpdate(sessionId);
+ }
+
+ @Override
+ public void onDestroySmartspaceSession(SmartspaceSessionId sessionId) {
+// Log.d(TAG, "onDestroySmartspaceSession mSessionCallbacks: " + mSessionCallbacks);
+ if (DEBUG) Log.d(TAG, "onDestroySmartspaceSession");
+ targets.remove(sessionId);
+ super.onDestroy();
+ sWatcher.destroyed.countDown();
+ }
+
+ @Override
+ public void onDestroy(SmartspaceSessionId sessionId) {
+// Log.d(TAG, "onDestroy mSessionCallbacks: " + mSessionCallbacks);
+ if (DEBUG) Log.d(TAG, "onDestroy");
+ super.onDestroy();
+ sWatcher.destroyed.countDown();
+ }
+
+
+ public static Watcher setWatcher() {
+ if (DEBUG) {
+ Log.d(TAG, "");
+ Log.d(TAG, "----------------------------------------------");
+ Log.d(TAG, " setWatcher");
+ }
+ if (sWatcher != null) {
+ throw new IllegalStateException("Set watcher with watcher already set");
+ }
+ sWatcher = new Watcher();
+ return sWatcher;
+ }
+
+ public static void clearWatcher() {
+ if (DEBUG) Log.d(TAG, "clearWatcher");
+ sWatcher = null;
+ }
+
+ public static final class Watcher {
+ public CountDownLatch created = new CountDownLatch(1);
+ public CountDownLatch destroyed = new CountDownLatch(1);
+ public CountDownLatch queried = new CountDownLatch(1);
+ public CountDownLatch queriedTwice = new CountDownLatch(2);
+
+ /**
+ * Can be used to verify that API specific service methods are called. Not a real mock as
+ * the system isn't talking to this directly, it has calls proxied to it.
+ */
+ public CtsSmartspaceService verifier;
+
+ public List<SmartspaceTarget> mSmartspaceTargets;
+
+ public void setTargets(List<SmartspaceTarget> targets) {
+ mSmartspaceTargets = targets;
+ }
+ }
+}
diff --git a/tests/smartspace/src/android/smartspace/cts/SmartspaceManagerTest.java b/tests/smartspace/src/android/smartspace/cts/SmartspaceManagerTest.java
new file mode 100644
index 0000000..936a835
--- /dev/null
+++ b/tests/smartspace/src/android/smartspace/cts/SmartspaceManagerTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.smartspace.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceManager;
+import android.app.smartspace.SmartspaceSession;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Process;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.RequiredServiceRule;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link SmartspaceManager}
+ *
+ * atest CtsSearchUiServiceTestCases
+ */
+@RunWith(AndroidJUnit4.class)
+public class SmartspaceManagerTest {
+
+ private static final String TAG = "SmartspaceManagerTest";
+ private static final boolean DEBUG = false;
+
+ private static final long VERIFY_TIMEOUT_MS = 5_000;
+ private static final long SERVICE_LIFECYCLE_TIMEOUT_MS = 20_000;
+ private static final String TEST_UI_SURFACE = "homescreen";
+ private static final String SMARTSPACE_ACTION_ID = "dummy_action_id";
+ private static final int TEST_NUM_PREDICTIONS = 10;
+ private static final String TEST_LAUNCH_LOCATION = "testCollapsedLocation";
+ private static final int TEST_ACTION = 2;
+
+ @Rule
+ public final RequiredServiceRule mRequiredServiceRule =
+ new RequiredServiceRule(Context.SMARTSPACE_SERVICE);
+
+ private SmartspaceManager mManager;
+ private SmartspaceSession mClient;
+ private CtsSmartspaceService.Watcher mWatcher;
+
+ @Before
+ public void setUp() throws Exception {
+ mWatcher = CtsSmartspaceService.setWatcher();
+ mManager = getContext().getSystemService(SmartspaceManager.class);
+ setService(CtsSmartspaceService.SERVICE_NAME);
+ SmartspaceConfig config = createSmartspaceConfig(TEST_UI_SURFACE);
+ mClient = createSmartspaceSession(config);
+ await(mWatcher.created, "Waiting for onCreate()");
+ reset(mWatcher.verifier);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ Log.d(TAG, "Starting tear down, watcher is: " + mWatcher);
+ mClient.destroy();
+ setService(null);
+ await(mWatcher.destroyed, "Waiting for onDestroy()");
+
+ mWatcher = null;
+ CtsSmartspaceService.clearWatcher();
+ }
+
+ @Test
+ public void testCreateSmartspaceSession() {
+ assertNotNull(mClient);
+ assertNotNull(mWatcher.verifier);
+ }
+
+ @Test
+ public void testDestroySession() {
+ SmartspaceSession localClient = createSmartspaceSession(createSmartspaceConfig("surface"));
+ localClient.destroy();
+ await(mWatcher.destroyed, "Waiting for onDestroy()");
+ }
+
+ @Test
+ public void testRequestSmartspaceUpdate() {
+ // Send a request for a smartspace update
+ SmartspaceTarget testTarget = SmartspaceTestUtils.getBasicSmartspaceTarget("id",
+ SmartspaceTestUtils.getTestComponentName(), Process.myUserHandle());
+ SmartspaceTargetEvent testEvent = new SmartspaceTargetEvent.Builder(
+ SmartspaceTargetEvent.EVENT_TARGET_INTERACTION).setSmartspaceTarget(
+ testTarget).setSmartspaceActionId("id").build();
+ mClient.notifySmartspaceEvent(testEvent);
+ mClient.registerSmartspaceUpdates(Executors.newSingleThreadExecutor(),
+ targets -> {
+ if (targets.size() > 0 && targets.get(0).equals(testTarget)) {
+ mWatcher.queried.countDown();
+ }
+ });
+ mClient.requestSmartspaceUpdate();
+ // Verify that the API received it
+ verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS).times(2))
+ .onRequestSmartspaceUpdate(any());
+ await(mWatcher.queried, "Waiting for updateSmartspaceTargets()");
+ }
+
+ @Test
+ public void testRequestSmartspaceUpdateForMultipleSessions() {
+ SmartspaceTarget testTarget = SmartspaceTestUtils.getBasicSmartspaceTarget("id",
+ SmartspaceTestUtils.getTestComponentName(), Process.myUserHandle());
+ SmartspaceTargetEvent testEvent = new SmartspaceTargetEvent.Builder(
+ SmartspaceTargetEvent.EVENT_TARGET_INTERACTION).setSmartspaceTarget(
+ testTarget).setSmartspaceActionId("id").build();
+
+ SmartspaceConfig config1 = createSmartspaceConfig("surface 1");
+ SmartspaceSession client1 = createSmartspaceSession(config1);
+ SmartspaceConfig config2 = createSmartspaceConfig("surface 2");
+ SmartspaceSession client2 = createSmartspaceSession(config2);
+ client1.registerSmartspaceUpdates(Executors.newSingleThreadExecutor(),
+ targets -> {
+ // Counting down only if the returned list only contains test target.
+ if (targets.size() > 0 && targets.get(0).equals(testTarget)) {
+ mWatcher.queriedTwice.countDown();
+ }
+ });
+ client2.registerSmartspaceUpdates(Executors.newSingleThreadExecutor(),
+ targets -> {
+ // Counting down only if the returned list is empty.
+ if (targets.isEmpty()) {
+ mWatcher.queriedTwice.countDown();
+ }
+ });
+ // Notifying the event only for client1.
+ client1.notifySmartspaceEvent(testEvent);
+ // Requesting update for both the clients
+ client1.requestSmartspaceUpdate();
+ client2.requestSmartspaceUpdate();
+ // Verify that the API received it 4 times, twice for each client, once while registering
+ // and once while requesting.
+ verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS).times(4))
+ .onRequestSmartspaceUpdate(any());
+ await(mWatcher.queriedTwice, "Waiting for updateSmartspaceTargets() to be called twice");
+ }
+
+ @Test
+ public void testNotifySmartspaceEvent() {
+ ComponentName componentName = new ComponentName("package_name", "class_name");
+ SmartspaceTarget target = new SmartspaceTarget.Builder("id",
+ componentName, Process.myUserHandle()).build();
+
+ SmartspaceTargetEvent smartspaceTargetEvent = new SmartspaceTargetEvent.Builder(
+ SmartspaceTargetEvent.EVENT_TARGET_BLOCK).setSmartspaceActionId(
+ SMARTSPACE_ACTION_ID).setSmartspaceTarget(target).build();
+
+ ArgumentCaptor<SmartspaceTargetEvent> arg = ArgumentCaptor.forClass(
+ SmartspaceTargetEvent.class);
+ // Send a request to notifySmartspaceUpdate
+ mClient.notifySmartspaceEvent(smartspaceTargetEvent);
+ // Verify that the API received it.
+ verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS))
+ .notifySmartspaceEvent(any(), arg.capture());
+ assertEquals(arg.getValue().getSmartspaceActionId(), SMARTSPACE_ACTION_ID);
+ assertEquals(arg.getValue().getEventType(), SmartspaceTargetEvent.EVENT_TARGET_BLOCK);
+ assertEquals(arg.getValue().getSmartspaceTarget().getComponentName(), componentName);
+ assertEquals(arg.getValue().getSmartspaceTarget().getUserHandle(), Process.myUserHandle());
+ assertEquals(arg.getValue().getSmartspaceTarget().getSmartspaceTargetId(), "id");
+
+ }
+
+ private void setService(String service) {
+ Log.d(TAG, "Setting smartspace service to " + service);
+ int userId = Process.myUserHandle().getIdentifier();
+ if (service != null) {
+ runShellCommand("cmd smartspace set temporary-service "
+ + userId + " " + service + " 60000");
+ } else {
+ runShellCommand("cmd smartspace set temporary-service " + userId);
+ }
+ }
+
+ private SmartspaceSession createSmartspaceSession(SmartspaceConfig config) {
+ return mManager.createSmartspaceSession(config);
+ }
+
+ private SmartspaceConfig createSmartspaceConfig(String uiSurface) {
+ return new SmartspaceConfig.Builder(
+ InstrumentationRegistry.getTargetContext(),
+ uiSurface).setSmartspaceTargetCount(TEST_NUM_PREDICTIONS).build();
+ }
+
+ private void await(@NonNull CountDownLatch latch, @NonNull String message) {
+ try {
+ assertWithMessage(message).that(
+ latch.await(SERVICE_LIFECYCLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("Interrupted while: " + message);
+ }
+ }
+
+ private void runShellCommand(String command) {
+ Log.d(TAG, "runShellCommand(): " + command);
+ try {
+ SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+ } catch (Exception e) {
+ throw new RuntimeException("Command '" + command + "' failed: ", e);
+ }
+ }
+
+ public static class ConsumerVerifier implements
+ Consumer<List<SmartspaceTarget>> {
+
+ private static List<SmartspaceTarget> mExpectedTargets;
+
+ public ConsumerVerifier(List<SmartspaceTarget> targets) {
+ mExpectedTargets = targets;
+ }
+
+ @Override
+ public void accept(List<SmartspaceTarget> actualTargets) {
+ if (DEBUG) {
+ Log.d(TAG, "ConsumerVerifier.accept targets.size= " + actualTargets.size());
+ Log.d(TAG, "ConsumerVerifier.accept target(1).packageName=" + actualTargets.get(
+ 0).getComponentName().getPackageName());
+ }
+ Assert.assertArrayEquals(actualTargets.toArray(), mExpectedTargets.toArray());
+ }
+ }
+
+ private static class RequestVerifier implements SmartspaceSession.Callback {
+ @Override
+ public void onTargetsAvailable(List<SmartspaceTarget> targets) {
+
+ }
+ }
+}
diff --git a/tests/smartspace/src/android/smartspace/cts/SmartspaceTestUtils.java b/tests/smartspace/src/android/smartspace/cts/SmartspaceTestUtils.java
new file mode 100644
index 0000000..d2a5ffb
--- /dev/null
+++ b/tests/smartspace/src/android/smartspace/cts/SmartspaceTestUtils.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.smartspace.cts;
+
+import android.app.smartspace.SmartspaceTarget;
+import android.content.ComponentName;
+import android.os.UserHandle;
+
+public class SmartspaceTestUtils {
+ public static SmartspaceTarget getBasicSmartspaceTarget(String id, ComponentName componentName, UserHandle userHandle){
+ return new SmartspaceTarget.Builder(id, componentName, userHandle).build();
+ }
+
+ public static ComponentName getTestComponentName() {
+ return new ComponentName("package name", "class name");
+ }
+}
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
index a78574b..c304806 100644
--- 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
@@ -36,6 +36,7 @@
import static android.appenumeration.cts.Constants.EXTRA_ERROR;
import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
+import static android.appenumeration.cts.Constants.EXTRA_REMOTE_READY_CALLBACK;
import static android.content.Intent.EXTRA_RETURN_RESULT;
import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
import static android.os.Process.INVALID_UID;
@@ -68,6 +69,9 @@
import android.util.SparseArray;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
public class TestActivity extends Activity {
@@ -87,6 +91,7 @@
backgroundHandler = new Handler(backgroundThread.getLooper());
super.onCreate(savedInstanceState);
handleIntent(getIntent());
+ onCommandReady(getIntent());
}
@Override
@@ -184,6 +189,11 @@
bindService(remoteCallback, packageName);
} else if (Constants.ACTION_GET_SYNCADAPTER_TYPES.equals(action)) {
sendSyncAdapterTypes(remoteCallback);
+ } else if (Constants.ACTION_AWAIT_PACKAGES_SUSPENDED.equals(action)) {
+ final String[] awaitPackages = intent.getBundleExtra(EXTRA_DATA)
+ .getStringArray(Intent.EXTRA_PACKAGES);
+ awaitSuspendedPackagesBroadcast(remoteCallback, Arrays.asList(awaitPackages),
+ Intent.ACTION_PACKAGES_SUSPENDED, TIMEOUT_MS);
} else {
sendError(remoteCallback, new Exception("unknown action " + action));
}
@@ -192,6 +202,13 @@
}
}
+ private void onCommandReady(Intent intent) {
+ final RemoteCallback callback = intent.getParcelableExtra(EXTRA_REMOTE_READY_CALLBACK);
+ if (callback != null) {
+ callback.sendResult(null);
+ }
+ }
+
private void awaitPackageBroadcast(RemoteCallback remoteCallback, String packageName,
String action, long timeoutMs) {
final IntentFilter filter = new IntentFilter(action);
@@ -214,6 +231,34 @@
token, timeoutMs);
}
+ private void awaitSuspendedPackagesBroadcast(RemoteCallback remoteCallback,
+ List<String> awaitList, String action, long timeoutMs) {
+ final IntentFilter filter = new IntentFilter(action);
+ final ArrayList<String> suspendedList = new ArrayList<>();
+ final Object token = new Object();
+ final Runnable sendResult = () -> {
+ final Bundle result = new Bundle();
+ result.putStringArray(Intent.EXTRA_PACKAGES, suspendedList.toArray(new String[] {}));
+ remoteCallback.sendResult(result);
+ finish();
+ };
+ registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Bundle extras = intent.getExtras();
+ final String[] changedList = extras.getStringArray(
+ Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ suspendedList.addAll(Arrays.stream(changedList).filter(
+ p -> awaitList.contains(p)).collect(Collectors.toList()));
+ if (suspendedList.size() == awaitList.size()) {
+ mainHandler.removeCallbacksAndMessages(token);
+ sendResult.run();
+ }
+ }
+ }, filter);
+ mainHandler.postDelayed(() -> sendResult.run(), token, timeoutMs);
+ }
+
private void sendGetInstalledPackages(RemoteCallback remoteCallback, int flags) {
String[] packages =
getPackageManager().getInstalledPackages(flags)
diff --git a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
index 5a3c12e..6321d04 100644
--- a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
+++ b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
@@ -178,8 +178,11 @@
public static final String ACTION_BIND_SERVICE = PKG_BASE + "cts.action.BIND_SERVICE";
public static final String ACTION_GET_SYNCADAPTER_TYPES =
PKG_BASE + "cts.action.GET_SYNCADAPTER_TYPES";
+ public static final String ACTION_AWAIT_PACKAGES_SUSPENDED =
+ PKG_BASE + "cts.action.AWAIT_PACKAGES_SUSPENDED";
public static final String EXTRA_REMOTE_CALLBACK = "remoteCallback";
+ public static final String EXTRA_REMOTE_READY_CALLBACK = "remoteReadyCallback";
public static final String EXTRA_ERROR = "error";
public static final String EXTRA_FLAGS = "flags";
public static final String EXTRA_DATA = "data";
diff --git a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
index a1adf19..39a8537 100644
--- a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
+++ b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
@@ -42,6 +42,7 @@
import static android.appenumeration.cts.Constants.EXTRA_ERROR;
import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
+import static android.appenumeration.cts.Constants.EXTRA_REMOTE_READY_CALLBACK;
import static android.appenumeration.cts.Constants.QUERIES_ACTIVITY_ACTION;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_PERM;
@@ -92,6 +93,7 @@
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.hasItemInArray;
@@ -626,7 +628,7 @@
public void broadcastAdded_notVisibleDoesNotReceive() throws Exception {
final Result result = sendCommand(QUERIES_NOTHING, TARGET_FILTERS,
/* targetUid */ INVALID_UID, /* intentExtra */ null,
- Constants.ACTION_AWAIT_PACKAGE_ADDED);
+ Constants.ACTION_AWAIT_PACKAGE_ADDED, /* waitForReady */ false);
runShellCommand("pm install " + TARGET_FILTERS_APK);
try {
result.await();
@@ -640,7 +642,7 @@
public void broadcastAdded_visibleReceives() throws Exception {
final Result result = sendCommand(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS,
/* targetUid */ INVALID_UID, /* intentExtra */ null,
- Constants.ACTION_AWAIT_PACKAGE_ADDED);
+ Constants.ACTION_AWAIT_PACKAGE_ADDED, /* waitForReady */ false);
runShellCommand("pm install " + TARGET_FILTERS_APK);
try {
Assert.assertEquals(TARGET_FILTERS,
@@ -654,7 +656,7 @@
public void broadcastRemoved_notVisibleDoesNotReceive() throws Exception {
final Result result = sendCommand(QUERIES_NOTHING, TARGET_FILTERS,
/* targetUid */ INVALID_UID, /* intentExtra */ null,
- Constants.ACTION_AWAIT_PACKAGE_REMOVED);
+ Constants.ACTION_AWAIT_PACKAGE_REMOVED, /* waitForReady */ false);
runShellCommand("pm install " + TARGET_FILTERS_APK);
try {
result.await();
@@ -668,7 +670,7 @@
public void broadcastRemoved_visibleReceives() throws Exception {
final Result result = sendCommand(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS,
/* targetUid */ INVALID_UID, /* intentExtra */ null,
- Constants.ACTION_AWAIT_PACKAGE_REMOVED);
+ Constants.ACTION_AWAIT_PACKAGE_REMOVED, /* waitForReady */ false);
runShellCommand("pm install " + TARGET_FILTERS_APK);
try {
Assert.assertEquals(TARGET_FILTERS,
@@ -679,6 +681,27 @@
}
@Test
+ public void broadcastSuspended_visibleReceives() throws Exception {
+ assertBroadcastSuspendedVisible(QUERIES_PACKAGE,
+ Arrays.asList(TARGET_NO_API, TARGET_SYNCADAPTER),
+ Arrays.asList(TARGET_NO_API, TARGET_SYNCADAPTER));
+ }
+
+ @Test
+ public void broadcastSuspended_notVisibleDoesNotReceive() throws Exception {
+ assertBroadcastSuspendedVisible(QUERIES_NOTHING,
+ Arrays.asList(),
+ Arrays.asList(TARGET_NO_API, TARGET_SYNCADAPTER));
+ }
+
+ @Test
+ public void broadcastSuspended_visibleReceivesAndNotVisibleDoesNotReceive() throws Exception {
+ assertBroadcastSuspendedVisible(QUERIES_ACTIVITY_ACTION,
+ Arrays.asList(TARGET_FILTERS),
+ Arrays.asList(TARGET_NO_API, TARGET_FILTERS));
+ }
+
+ @Test
public void queriesResolver_grantsVisibilityToProvider() throws Exception {
assertNotVisible(QUERIES_NOTHING_PROVIDER, QUERIES_NOTHING_PERM);
@@ -796,6 +819,24 @@
packageNames, not(hasItemInArray(targetPackageName)));
}
+ private void assertBroadcastSuspendedVisible(String sourcePackageName,
+ List<String> expectedVisiblePackages, List<String> packagesToSuspend)
+ throws Exception {
+ final Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_PACKAGES, packagesToSuspend.toArray(new String[] {}));
+ final Result result = sendCommand(sourcePackageName, /* targetPackageName */ null,
+ /* targetUid */ INVALID_UID, extras, Constants.ACTION_AWAIT_PACKAGES_SUSPENDED,
+ /* waitForReady */ true);
+ try {
+ setPackagesSuspended(true, packagesToSuspend);
+ final String[] suspendedPackages = result.await().getStringArray(Intent.EXTRA_PACKAGES);
+ assertThat(suspendedPackages, arrayContainingInAnyOrder(
+ expectedVisiblePackages.toArray()));
+ } finally {
+ setPackagesSuspended(false, packagesToSuspend);
+ }
+ }
+
private PackageInfo getPackageInfo(String sourcePackageName, String targetPackageName)
throws Exception {
Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
@@ -935,12 +976,24 @@
.toArray(String[]::new);
}
+ private void setPackagesSuspended(boolean suspend, List<String> packages) {
+ final StringBuilder cmd = new StringBuilder("pm ");
+ if (suspend) {
+ cmd.append("suspend");
+ } else {
+ cmd.append("unsuspend");
+ }
+ packages.stream().forEach(p -> cmd.append(" ").append(p));
+ runShellCommand(cmd.toString());
+ }
+
interface Result {
Bundle await() throws Exception;
}
private Result sendCommand(String sourcePackageName, @Nullable String targetPackageName,
- int targetUid, @Nullable Parcelable intentExtra, String action) {
+ int targetUid, @Nullable Parcelable intentExtra, String action, boolean waitForReady)
+ throws Exception {
final Intent intent = new Intent(action)
.setComponent(new ComponentName(sourcePackageName, ACTIVITY_CLASS_TEST))
// data uri unique to each activity start to ensure actual launch and not just
@@ -972,7 +1025,11 @@
},
sResponseHandler);
intent.putExtra(EXTRA_REMOTE_CALLBACK, callback);
- InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent);
+ if (waitForReady) {
+ startAndWaitForCommandReady(intent);
+ } else {
+ InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent);
+ }
return () -> {
if (!latch.block(TimeUnit.SECONDS.toMillis(10))) {
throw new TimeoutException(
@@ -986,11 +1043,23 @@
};
}
+ private void startAndWaitForCommandReady(Intent intent) throws Exception {
+ final ConditionVariable latchForReady = new ConditionVariable();
+ final RemoteCallback readyCallback = new RemoteCallback(bundle -> latchForReady.open(),
+ sResponseHandler);
+ intent.putExtra(EXTRA_REMOTE_READY_CALLBACK, readyCallback);
+ InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent);
+ if (!latchForReady.block(TimeUnit.SECONDS.toMillis(10))) {
+ throw new TimeoutException(
+ "Latch timed out while awiating a response from command " + intent.getAction());
+ }
+ }
+
private Bundle sendCommandBlocking(String sourcePackageName, @Nullable String targetPackageName,
@Nullable Parcelable intentExtra, String action)
throws Exception {
final Result result = sendCommand(sourcePackageName, targetPackageName,
- /* targetUid */ INVALID_UID, intentExtra, action);
+ /* targetUid */ INVALID_UID, intentExtra, action, /* waitForReady */ false);
return result.await();
}
@@ -998,7 +1067,7 @@
@Nullable Parcelable intentExtra, String action)
throws Exception {
final Result result = sendCommand(sourcePackageName, /* targetPackageName */ null,
- targetUid, intentExtra, action);
+ targetUid, intentExtra, action, /* waitForReady */ false);
return result.await();
}
}
diff --git a/tests/tests/appop/AndroidManifest.xml b/tests/tests/appop/AndroidManifest.xml
index 434821e..bd090dc 100644
--- a/tests/tests/appop/AndroidManifest.xml
+++ b/tests/tests/appop/AndroidManifest.xml
@@ -26,8 +26,10 @@
<attribution android:tag="secondProxyAttribution" android:label="@string/dummyLabel" />
<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.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
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 dae5d3a..6c4fcc6 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
@@ -28,6 +28,7 @@
import android.app.AppOpsManager.MODE_DEFAULT
import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.MODE_IGNORED
+import android.app.AppOpsManager.OPSTR_PICTURE_IN_PICTURE
import android.app.AppOpsManager.OPSTR_READ_CALENDAR
import android.app.AppOpsManager.OPSTR_RECORD_AUDIO
import android.app.AppOpsManager.OPSTR_WIFI_SCAN
@@ -510,33 +511,33 @@
fun testNonHistoricalStatePersistence() {
// Put a package and uid level data
runWithShellPermissionIdentity {
- mAppOps.setMode(OPSTR_RECORD_AUDIO, Process.myUid(),
+ mAppOps.setMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(),
mOpPackageName, MODE_IGNORED)
- mAppOps.setUidMode(OPSTR_RECORD_AUDIO, Process.myUid(), MODE_ERRORED)
+ mAppOps.setUidMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(), MODE_ERRORED)
// Write the data to disk and read it
mAppOps.reloadNonHistoricalState()
}
// Verify the uid state is preserved
- assertSame(mAppOps.unsafeCheckOpNoThrow(OPSTR_RECORD_AUDIO,
+ assertSame(mAppOps.unsafeCheckOpNoThrow(OPSTR_PICTURE_IN_PICTURE,
Process.myUid(), mOpPackageName), MODE_ERRORED)
runWithShellPermissionIdentity {
// Clear the uid state
- mAppOps.setUidMode(OPSTR_RECORD_AUDIO, Process.myUid(),
- AppOpsManager.opToDefaultMode(OPSTR_RECORD_AUDIO))
+ mAppOps.setUidMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(),
+ AppOpsManager.opToDefaultMode(OPSTR_PICTURE_IN_PICTURE))
}
// Verify the package state is preserved
- assertSame(mAppOps.unsafeCheckOpNoThrow(OPSTR_RECORD_AUDIO,
+ assertSame(mAppOps.unsafeCheckOpNoThrow(OPSTR_PICTURE_IN_PICTURE,
Process.myUid(), mOpPackageName), MODE_IGNORED)
runWithShellPermissionIdentity {
// Clear the uid state
- val defaultMode = AppOpsManager.opToDefaultMode(OPSTR_RECORD_AUDIO)
- mAppOps.setUidMode(OPSTR_RECORD_AUDIO, Process.myUid(), defaultMode)
- mAppOps.setMode(OPSTR_RECORD_AUDIO, Process.myUid(),
+ val defaultMode = AppOpsManager.opToDefaultMode(OPSTR_PICTURE_IN_PICTURE)
+ mAppOps.setUidMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(), defaultMode)
+ mAppOps.setMode(OPSTR_PICTURE_IN_PICTURE, Process.myUid(),
mOpPackageName, defaultMode)
}
}
diff --git a/tests/tests/assist/TEST_MAPPING b/tests/tests/assist/TEST_MAPPING
new file mode 100644
index 0000000..c694fc6
--- /dev/null
+++ b/tests/tests/assist/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsAssistTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/tests/assist/common/src/android/assist/common/Utils.java b/tests/tests/assist/common/src/android/assist/common/Utils.java
index 50e4d42..86062b2 100755
--- a/tests/tests/assist/common/src/android/assist/common/Utils.java
+++ b/tests/tests/assist/common/src/android/assist/common/Utils.java
@@ -48,6 +48,7 @@
public static final String ASSIST_STRUCTURE_KEY = "assist_structure";
public static final String ASSIST_CONTENT_KEY = "assist_content";
public static final String ASSIST_BUNDLE_KEY = "assist_bundle";
+ public static final String ASSIST_IS_ACTIVITY_ID_NULL = "assist_is_activity_id_null";
public static final String ASSIST_SCREENSHOT_KEY = "assist_screenshot";
public static final String SCREENSHOT_COLOR_KEY = "set_screenshot_color";
public static final String COMPARE_SCREENSHOT_KEY = "compare_screenshot";
@@ -55,6 +56,7 @@
public static final String DISPLAY_HEIGHT_KEY = "dislay_height";
public static final String SCROLL_X_POSITION = "scroll_x_position";
public static final String SCROLL_Y_POSITION = "scroll_y_position";
+ public static final String SHOW_SESSION_FLAGS_TO_SET = "show_session_flags_to_set";
/** Lifecycle Test intent constants */
public static final String LIFECYCLE_PREFIX = ACTION_PREFIX + "lifecycle_";
diff --git a/tests/tests/assist/service/src/android/assist/service/MainInteractionService.java b/tests/tests/assist/service/src/android/assist/service/MainInteractionService.java
index d5e3a6d..5b468e4 100644
--- a/tests/tests/assist/service/src/android/assist/service/MainInteractionService.java
+++ b/tests/tests/assist/service/src/android/assist/service/MainInteractionService.java
@@ -16,6 +16,7 @@
package android.assist.service;
+import static android.assist.common.Utils.SHOW_SESSION_FLAGS_TO_SET;
import static android.service.voice.VoiceInteractionSession.SHOW_WITH_ASSIST;
import static android.service.voice.VoiceInteractionSession.SHOW_WITH_SCREENSHOT;
@@ -118,11 +119,12 @@
if (extras == null) {
extras = new Bundle();
}
-
+ int showSessionFlags = extras.getInt(SHOW_SESSION_FLAGS_TO_SET,
+ SHOW_WITH_ASSIST | SHOW_WITH_SCREENSHOT);
extras.putString(Utils.TESTCASE_TYPE, mIntent.getStringExtra(Utils.TESTCASE_TYPE));
extras.putParcelable(Utils.EXTRA_REMOTE_CALLBACK, mRemoteCallback);
MainInteractionService.this.showSession(
- extras, SHOW_WITH_ASSIST | SHOW_WITH_SCREENSHOT);
+ extras, showSessionFlags);
} else {
Log.e(TAG, "MainInteractionServiceBroadcastReceiver: invalid action " + action);
}
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 ad8815f..a535d99 100644
--- a/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java
+++ b/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java
@@ -44,6 +44,7 @@
private boolean hasReceivedAssistData = false;
private boolean hasReceivedScreenshot = false;
+ private boolean mScreenshotNeeded = true;
private int mCurColor;
private int mDisplayHeight;
private int mDisplayWidth;
@@ -104,13 +105,11 @@
@Override
public void onShow(Bundle args, int showFlags) {
- if ((showFlags & SHOW_WITH_ASSIST) == 0) {
- return;
- }
if (args == null) {
Log.e(TAG, "onshow() received null args");
return;
}
+ mScreenshotNeeded = (showFlags & SHOW_WITH_SCREENSHOT) != 0;
mTestName = args.getString(Utils.TESTCASE_TYPE, "");
mCurColor = args.getInt(Utils.SCREENSHOT_COLOR_KEY);
mDisplayHeight = args.getInt(Utils.DISPLAY_HEIGHT_KEY);
@@ -149,15 +148,18 @@
}
@Override
- @SuppressWarnings("deprecation")
- public void onHandleAssist(/*@Nullable */Bundle data, /*@Nullable*/ AssistStructure structure,
- /*@Nullable*/ AssistContent content) {
+ public void onHandleAssist(AssistState state) {
+ super.onHandleAssist(state);
+ Bundle data = state.getAssistData();
+ AssistStructure structure = state.getAssistStructure();
+ AssistContent content = state.getAssistContent();
+
Log.i(TAG, "onHandleAssist");
Log.i(TAG,
String.format("Bundle: %s, Structure: %s, Content: %s", data, structure, content));
- super.onHandleAssist(data, structure, content);
// send to test to verify that this is accurate.
+ mAssistData.putBoolean(Utils.ASSIST_IS_ACTIVITY_ID_NULL, state.getActivityId() == null);
mAssistData.putParcelable(Utils.ASSIST_STRUCTURE_KEY, structure);
mAssistData.putParcelable(Utils.ASSIST_CONTENT_KEY, content);
mAssistData.putBundle(Utils.ASSIST_BUNDLE_KEY, data);
@@ -166,13 +168,6 @@
}
@Override
- @SuppressWarnings("deprecation")
- public void onHandleAssistSecondary(Bundle data, AssistStructure structure,
- AssistContent content, int index, int count) {
- Log.e(TAG, "onHandleAssistSecondary() called instead of onHandleAssist()");
- }
-
- @Override
public void onAssistStructureFailure(Throwable failure) {
Log.e(TAG, "onAssistStructureFailure(): D'OH!!!", failure);
}
@@ -229,7 +224,7 @@
private void maybeBroadcastResults() {
if (!hasReceivedAssistData) {
Log.i(TAG, "waiting for assist data before broadcasting results");
- } else if (!hasReceivedScreenshot) {
+ } else if (mScreenshotNeeded && !hasReceivedScreenshot) {
Log.i(TAG, "waiting for screenshot before broadcasting results");
} else {
Bundle bundle = new Bundle();
diff --git a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
index 2aecbd6..8c25dea 100644
--- a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
@@ -65,7 +65,6 @@
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;
@@ -120,6 +119,7 @@
protected ActivityManager mActivityManager;
private TestStartActivity mTestActivity;
+ protected boolean mIsActivityIdNull;
protected AssistContent mAssistContent;
protected AssistStructure mAssistStructure;
protected boolean mScreenshot;
@@ -163,6 +163,7 @@
mAssistStructure = null;
mAssistContent = null;
mAssistBundle = null;
+ mIsActivityIdNull = false;
mActionLatchReceiver = new ActionLatchReceiver();
@@ -293,7 +294,11 @@
* Send broadcast to MainInteractionService to start a session
*/
protected AutoResetLatch startSession() {
- return startSession(mTestName, new Bundle());
+ return startSession(new Bundle());
+ }
+
+ protected AutoResetLatch startSession(Bundle extras) {
+ return startSession(mTestName, extras);
}
protected AutoResetLatch startSession(String testName, Bundle extras) {
@@ -331,6 +336,19 @@
}
/**
+ * Checks the nullness of the received
+ * {@link android.service.voice.VoiceInteractionSession.ActivityId}.
+ *
+ * @param isActivityIdNull True if activityId should be null.
+ */
+ protected void verifyActivityIdNullness(boolean isActivityIdNull) {
+ if (mIsActivityIdNull != isActivityIdNull) {
+ fail(String.format("Should %s have been null - ActivityId: %s",
+ isActivityIdNull ? "" : "not", mIsActivityIdNull));
+ }
+ }
+
+ /**
* Checks that the nullness of values are what we expect.
*
* @param isBundleNull True if assistBundle should be null.
@@ -645,6 +663,7 @@
}
protected void setAssistResults(Bundle assistData) {
+ mIsActivityIdNull = assistData.getBoolean(Utils.ASSIST_IS_ACTIVITY_ID_NULL);;
mAssistBundle = assistData.getBundle(Utils.ASSIST_BUNDLE_KEY);
mAssistStructure = assistData.getParcelable(Utils.ASSIST_STRUCTURE_KEY);
mAssistContent = assistData.getParcelable(Utils.ASSIST_CONTENT_KEY);
diff --git a/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java b/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
index d465880..74468ff 100644
--- a/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
+++ b/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
@@ -15,6 +15,8 @@
*/
package android.assist.cts;
+import static android.assist.common.Utils.SHOW_SESSION_FLAGS_TO_SET;
+
import android.assist.common.AutoResetLatch;
import android.assist.common.Utils;
import android.content.Intent;
@@ -67,4 +69,23 @@
assertWithMessage("Wrong value for EXTRA_ASSIST_UID").that(actualUid)
.isEqualTo(expectedUid);
}
+
+ @Test
+ public void testAssistContentAndDataNullWhenNoFlagsToShowSession() throws Exception {
+ if (mActivityManager.isLowRamDevice()) {
+ Log.d(TAG, "Not running assist tests on low-RAM device.");
+ return;
+ }
+ startTest(TEST_CASE_TYPE);
+ waitForAssistantToBeReady();
+ start3pApp(TEST_CASE_TYPE);
+
+ Bundle bundle = new Bundle();
+ bundle.putInt(SHOW_SESSION_FLAGS_TO_SET, 0);
+ final AutoResetLatch latch = startSession(bundle);
+ waitForContext(latch);
+
+ verifyActivityIdNullness(/* isActivityIdNull = */ false);
+ verifyAssistDataNullness(true, true, true, true);
+ }
}
diff --git a/tests/tests/bionic/Android.build.copy.libs.mk b/tests/tests/bionic/Android.build.copy.libs.mk
index 8900a75..628eeed 100644
--- a/tests/tests/bionic/Android.build.copy.libs.mk
+++ b/tests/tests/bionic/Android.build.copy.libs.mk
@@ -22,6 +22,12 @@
elftls_dlopen_ie_error_helper/elftls_dlopen_ie_error_helper \
exec_linker_helper/exec_linker_helper \
exec_linker_helper_lib.so \
+ heap_tagging_async_helper/heap_tagging_async_helper \
+ heap_tagging_disabled_helper/heap_tagging_disabled_helper \
+ heap_tagging_static_sync_helper/heap_tagging_static_sync_helper \
+ heap_tagging_static_async_helper/heap_tagging_static_async_helper \
+ heap_tagging_static_disabled_helper/heap_tagging_static_disabled_helper \
+ heap_tagging_sync_helper/heap_tagging_sync_helper \
inaccessible_libs/libtestshared.so \
inaccessible_libs/libtestshared.so \
ld_config_test_helper/ld_config_test_helper \
diff --git a/tests/tests/bluetooth/Android.bp b/tests/tests/bluetooth/Android.bp
index 8ce7efb..ee27a18 100644
--- a/tests/tests/bluetooth/Android.bp
+++ b/tests/tests/bluetooth/Android.bp
@@ -22,13 +22,15 @@
static_libs: [
"ctstestrunner-axt",
"bluetooth-test-util-lib",
+ "compatibility-device-util-axt",
],
libs: [
"android.test.runner",
"android.test.base",
],
srcs: ["src/**/*.java"],
- sdk_version: "current",
+ // Allows access to system apis
+ platform_apis: true,
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/tests/bluetooth/AndroidManifest.xml b/tests/tests/bluetooth/AndroidManifest.xml
index 24cd01b..d30fd48 100644
--- a/tests/tests/bluetooth/AndroidManifest.xml
+++ b/tests/tests/bluetooth/AndroidManifest.xml
@@ -19,6 +19,8 @@
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
new file mode 100644
index 0000000..46fa24c
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.test.AndroidTestCase;
+
+public class BluetoothDeviceTest extends AndroidTestCase {
+
+ private boolean mHasBluetooth;
+ private BluetoothAdapter mAdapter;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_BLUETOOTH);
+
+ if (mHasBluetooth) {
+ BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
+ mAdapter = manager.getAdapter();
+ assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+ }
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ if (mHasBluetooth) {
+ assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+ mAdapter = null;
+ }
+ }
+
+ public void test_setAlias_getAlias() {
+ if (!mHasBluetooth) {
+ // Skip the test if bluetooth is not present.
+ return;
+ }
+
+ int userId = mContext.getUser().getIdentifier();
+ String packageName = mContext.getOpPackageName();
+ String deviceAddress = "00:11:22:AA:BB:CC";
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress);
+ // Verifies that when there is no alias, we return the device name
+ assertNull(device.getAlias());
+
+ String testDeviceAlias = "Test Device Alias";
+
+ // This should throw a SecurityException because there is no CDM association
+ try {
+ device.setAlias(testDeviceAlias);
+ fail("BluetoothDevice alias was able to be set without a CDM association without having"
+ + "BLUETOOTH_PRIVILEGED permission");
+ } catch (SecurityException ex) {
+ assertNull(device.getAlias());
+ }
+
+ runShellCommand(String.format(
+ "cmd companiondevice associate %d %s %s", userId, packageName, deviceAddress));
+ String output = runShellCommand("dumpsys companiondevice");
+ assertTrue("Package name missing from output", output.contains(packageName));
+ assertTrue("Device address missing from output", output.contains(deviceAddress));
+ try {
+ Thread.sleep(1000);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ /*
+ * Device properties don't exist for non-existent BluetoothDevice, so calling setAlias with
+ * permissions should return false
+ */
+ assertFalse(device.setAlias(testDeviceAlias));
+ runShellCommand(String.format(
+ "cmd companiondevice disassociate %d %s %s", userId, packageName, deviceAddress));
+ }
+}
diff --git a/tests/tests/car/AndroidManifest.xml b/tests/tests/car/AndroidManifest.xml
index 6435b57..44d2fa3 100644
--- a/tests/tests/car/AndroidManifest.xml
+++ b/tests/tests/car/AndroidManifest.xml
@@ -28,6 +28,8 @@
<uses-permission android:name="android.car.permission.READ_CAR_POWER_POLICY" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<!-- Allow query of any normal app on the device -->
diff --git a/tests/tests/car/src/android/car/cts/CarWatchdogManagerTest.java b/tests/tests/car/src/android/car/cts/CarWatchdogManagerTest.java
new file mode 100644
index 0000000..5e81672
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/CarWatchdogManagerTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 android.car.Car;
+import android.car.watchdog.CarWatchdogManager;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class CarWatchdogManagerTest extends CarApiTestBase {
+ private static String TAG = CarWatchdogManagerTest.class.getSimpleName();
+
+ private CarWatchdogManager mCarWatchdogManager;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mCarWatchdogManager = (CarWatchdogManager) getCar().getCarManager(Car.CAR_WATCHDOG_SERVICE);
+ }
+
+ @Test
+ public void testListenIoOveruse() {
+ /**
+ * TODO(b/178199164): Listen for disk I/O overuse.
+ * 1. Add resource overuse listener for I/O resource.
+ * 2. Write huge amount of data to disk such that it exceeds the threshold.
+ * 3. Fetch the I/O overuse stats and check whether the written bytes are >= total written
+ * bytes.
+ * 4. Check whether the resource overuse listener is called and the provided written bytes
+ * are >= total written bytes.
+ * 5. Remove the resource overuse listener.
+ */
+ }
+}
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java
index 820c27a..45bb647 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java
@@ -16,6 +16,8 @@
package android.carrierapi.cts;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -67,6 +69,9 @@
public class BugreportManagerTest {
private static final String TAG = "BugreportManagerTest";
+ // See BugreportManagerServiceImpl#BUGREPORT_SERVICE.
+ private static final String BUGREPORT_SERVICE = "bugreportd";
+
private static final long BUGREPORT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
private static final long UIAUTOMATOR_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
// This value is defined in dumpstate.cpp:TELEPHONY_REPORT_USER_CONSENT_TIMEOUT_MS. Because the
@@ -101,6 +106,7 @@
.isTrue();
mBugreportManager = context.getSystemService(BugreportManager.class);
+ killCurrentBugreportIfRunning();
mBugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip");
mBugreportFd = parcelFd(mBugreportFile);
// Should never be written for anything a carrier app can trigger; several tests assert that
@@ -113,6 +119,7 @@
public void tearDown() throws Exception {
FileUtils.closeQuietly(mBugreportFd);
FileUtils.closeQuietly(mScreenshotFd);
+ killCurrentBugreportIfRunning();
}
@Test
@@ -334,6 +341,14 @@
}
}
+ /**
+ * Kills the current bugreport if one is in progress to prevent failing test cases from
+ * cascading into other cases and causing flakes.
+ */
+ private static void killCurrentBugreportIfRunning() throws Exception {
+ runShellCommand("setprop ctl.stop " + BUGREPORT_SERVICE);
+ }
+
/** Allow/deny the consent dialog to sharing bugreport data, or just check existence. */
private enum ConsentReply {
// Touch the positive button.
diff --git a/tests/tests/content/Android.bp b/tests/tests/content/Android.bp
index 74fb472..77eb483 100644
--- a/tests/tests/content/Android.bp
+++ b/tests/tests/content/Android.bp
@@ -35,6 +35,7 @@
"android.test.mock",
],
static_libs: [
+ "apache-commons-compress",
"compatibility-device-util-axt",
"ctstestrunner-axt",
"services.core",
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 69dac6e..ea3b4ab 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -32,7 +32,8 @@
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- <uses-permission android:name="android.content.cts.permission.TEST_GRANTED"/>
+ <uses-permission android:name="android.content.cts.permission.TEST_GRANTED"
+ android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index d0d7229..4144f57 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -446,6 +446,10 @@
}
}
+ public void testRequestManageMedia() {
+ assertCanBeHandled(new Intent(Settings.ACTION_REQUEST_MANAGE_MEDIA));
+ }
+
public void testInteractAcrossProfilesSettings() {
PackageManager packageManager = mContext.getPackageManager();
if (packageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_PROFILES)) {
@@ -494,6 +498,9 @@
}
public void testPowerUsageSummarySettings() {
+ if(FeatureUtil.isWatch()){
+ return;
+ }
if (isBatteryPresent()) {
assertCanBeHandled(new Intent(Intent.ACTION_POWER_USAGE_SUMMARY));
}
@@ -517,6 +524,9 @@
}
public void testRequestSetAutofillServiceIntent() {
+ if (FeatureUtil.isWatch()) {
+ return;
+ }
Intent intent = new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
.setData(Uri.parse("package:android.content.cts"));
assertCanBeHandled(intent);
diff --git a/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java b/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
index 6c33f4a..f56ede2 100644
--- a/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
+++ b/tests/tests/content/src/android/content/cts/ClipDescriptionTest.java
@@ -251,6 +251,6 @@
intent.setClassName(mContext.getPackageName(), clazz.getName());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
- mUiDevice.wait(Until.hasObject(By.clazz(clazz)), 5000);
+ mUiDevice.wait(Until.hasObject(By.clazz(clazz)), 15000);
}
}
diff --git a/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java b/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java
index f507272..e2cca56 100644
--- a/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java
+++ b/tests/tests/content/src/android/content/cts/ClipboardManagerTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.Manifest;
import android.app.Activity;
@@ -34,6 +35,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
@@ -53,13 +55,13 @@
@RunWith(AndroidJUnit4.class)
//@AppModeFull // TODO(Instant) Should clip board data be visible?
public class ClipboardManagerTest {
- private Context mContext;
+ private final Context mContext = InstrumentationRegistry.getTargetContext();
private ClipboardManager mClipboardManager;
private UiDevice mUiDevice;
@Before
public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
+ assumeTrue("Skipping Test: Wear-Os does not support ClipboardService", hasAutoFillFeature());
mClipboardManager = mContext.getSystemService(ClipboardManager.class);
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
mUiDevice.wakeUp();
@@ -319,7 +321,7 @@
intent.setClassName(mContext.getPackageName(), clazz.getName());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
- mUiDevice.wait(Until.hasObject(By.clazz(clazz)), 5000);
+ mUiDevice.wait(Until.hasObject(By.clazz(clazz)), 15000);
}
private class ExpectedClipItem {
@@ -398,4 +400,9 @@
assertNull(item.getUri());
}
}
+
+ private boolean hasAutoFillFeature() {
+ return mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_AUTOFILL);
+ }
}
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
index 517451e..05ecb78 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import android.annotation.NonNull;
import android.app.UiAutomation;
import android.content.ComponentName;
import android.content.Context;
@@ -44,10 +45,13 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.incfs.install.IBlockTransformer;
import com.android.incfs.install.IncrementalInstallSession;
+import com.android.incfs.install.PendingBlock;
import libcore.io.IoUtils;
+import org.apache.commons.compress.compressors.lz4.BlockLZ4CompressorOutputStream;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
@@ -63,6 +67,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
@@ -221,6 +227,96 @@
assertTrue(isAppInstalled(TEST_APP_PACKAGE));
}
+ /**
+ * Compress the data if the compressed size is < original size, otherwise return the original
+ * data.
+ */
+ private static ByteBuffer maybeCompressPage(ByteBuffer pageData) {
+ pageData.mark();
+ ByteArrayOutputStream compressedByteStream = new ByteArrayOutputStream();
+ try (BlockLZ4CompressorOutputStream compressor =
+ new BlockLZ4CompressorOutputStream(compressedByteStream)) {
+ Channels.newChannel(compressor).write(pageData);
+ // This is required to make sure the bytes are written to the output
+ compressor.finish();
+ } catch (IOException impossible) {
+ throw new AssertionError(impossible);
+ } finally {
+ pageData.reset();
+ }
+
+ byte[] compressedBytes = compressedByteStream.toByteArray();
+ if (compressedBytes.length < pageData.remaining()) {
+ return ByteBuffer.wrap(compressedBytes);
+ }
+ return pageData;
+ }
+
+ static final class CompressedPendingBlock extends PendingBlock {
+ final ByteBuffer mPageData;
+
+ CompressedPendingBlock(PendingBlock block) throws IOException {
+ super(block);
+
+ final ByteBuffer buffer = ByteBuffer.allocate(super.getBlockSize());
+ super.readBlockData(buffer);
+ buffer.flip(); // switch to read mode
+
+ if (super.getType() == Type.APK_DATA) {
+ mPageData = maybeCompressPage(buffer);
+ } else {
+ mPageData = buffer;
+ }
+ }
+
+ public Compression getCompression() {
+ return this.getBlockSize() < super.getBlockSize() ? Compression.LZ4 : Compression.NONE;
+ }
+
+ public short getBlockSize() {
+ return (short) mPageData.remaining();
+ }
+
+ public void readBlockData(ByteBuffer buffer) throws IOException {
+ mPageData.mark();
+ buffer.put(mPageData);
+ mPageData.reset();
+ }
+ }
+
+ static final class CompressingBlockTransformer implements IBlockTransformer {
+ @Override
+ @NonNull
+ public PendingBlock transform(@NonNull PendingBlock block) throws IOException {
+ return new CompressedPendingBlock(block);
+ }
+ }
+
+ @LargeTest
+ @Test
+ public void testInstallWithStreamingAndCompression() throws Exception {
+ final String apk = createApkPath(TEST_APK);
+ final String idsig = createApkPath(TEST_APK_IDSIG);
+ mSession =
+ new IncrementalInstallSession.Builder()
+ .addApk(Paths.get(apk), Paths.get(idsig))
+ .addExtraArgs("-t", "-i", CTS_PACKAGE_NAME)
+ .setLogger(new IncrementalDeviceConnection.Logger())
+ .setBlockTransformer(new CompressingBlockTransformer())
+ .build();
+ getUiAutomation().adoptShellPermissionIdentity();
+ try {
+ mSession.start(
+ Executors.newSingleThreadExecutor(),
+ new IncrementalDeviceConnection.Factory(
+ IncrementalDeviceConnection.ConnectionType.RELIABLE));
+ mSession.waitForInstallCompleted(30, TimeUnit.SECONDS);
+ } finally {
+ getUiAutomation().dropShellPermissionIdentity();
+ }
+ assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+ }
+
@LargeTest
@Test
public void testInstallWithStreamingUnreliableConnection() throws Exception {
@@ -713,6 +809,24 @@
doTestInstallSysTrace(TEST_APK_PROFILEABLE);
}
+ @LargeTest
+ @Test
+ public void testInstallSysTraceNoReadlogs() throws Exception {
+ setSystemProperty("debug.incremental.enforce_readlogs_max_interval_for_system_dataloaders",
+ "1");
+ setSystemProperty("debug.incremental.readlogs_max_interval_sec", "0");
+
+ final int atraceDumpIterations = 30;
+ final int atraceDumpDelayMs = 100;
+ final String expected = "|page_read:";
+
+ // We don't expect any readlogs with 0sec interval.
+ assertFalse(
+ "Page reads (" + expected + ") were found in atrace dump",
+ checkSysTraceForSubstring(TEST_APK, expected, atraceDumpIterations,
+ atraceDumpDelayMs));
+ }
+
private boolean checkSysTraceForSubstring(String testApk, final String expected,
int atraceDumpIterations, int atraceDumpDelayMs) throws Exception {
final int installIterations = 3;
@@ -998,6 +1112,9 @@
assertEquals(null, getSplits(TEST_APP_PACKAGE));
setDeviceProperty("incfs_default_timeouts", null);
setDeviceProperty("known_digesters_list", null);
+ setSystemProperty("debug.incremental.enforce_readlogs_max_interval_for_system_dataloaders",
+ "0");
+ setSystemProperty("debug.incremental.readlogs_max_interval_sec", "10000");
IoUtils.closeQuietly(mSession);
mSession = null;
}
@@ -1012,5 +1129,9 @@
}
}
+ private void setSystemProperty(String name, String value) throws Exception {
+ executeShellCommand("setprop " + name + " " + value);
+ }
+
}
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 4db7f8e..1703a91 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -755,6 +755,19 @@
"android.permission.ACCESS_NETWORK_STATE",
"android.content.cts.permission.TEST_GRANTED");
+ // Check usesPermissionFlags
+ for (int i = 0; i < pkgInfo.requestedPermissions.length; i++) {
+ final String name = pkgInfo.requestedPermissions[i];
+ final int flags = pkgInfo.requestedPermissionsFlags[i];
+ final boolean neverForLocation = (flags
+ & PackageInfo.REQUESTED_PERMISSION_NEVER_FOR_LOCATION) != 0;
+ if ("android.content.cts.permission.TEST_GRANTED".equals(name)) {
+ assertTrue(name + " with flags " + flags, neverForLocation);
+ } else {
+ assertFalse(name + " with flags " + flags, neverForLocation);
+ }
+ }
+
// Check declared permissions
PermissionInfo declaredPermission = (PermissionInfo) findPackageItemOrFail(
pkgInfo.permissions, CALL_ABROAD_PERMISSION_NAME);
diff --git a/tests/tests/gamemanager/AndroidManifest.xml b/tests/tests/gamemanager/AndroidManifest.xml
index 2433674..ca874b9 100644
--- a/tests/tests/gamemanager/AndroidManifest.xml
+++ b/tests/tests/gamemanager/AndroidManifest.xml
@@ -18,7 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.gamemanager.cts">
- <application>
+ <application android:appCategory="game">
<uses-library android:name="android.test.runner"/>
<activity android:name=".GameManagerCtsActivity"
android:label="GameManagerCtsActivity"
diff --git a/tests/tests/gamemanager/src/android/settings/cts/GameManagerCtsActivity.java b/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerCtsActivity.java
similarity index 100%
rename from tests/tests/gamemanager/src/android/settings/cts/GameManagerCtsActivity.java
rename to tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerCtsActivity.java
diff --git a/tests/tests/gamemanager/src/android/settings/cts/GameManagerTest.java b/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java
similarity index 100%
rename from tests/tests/gamemanager/src/android/settings/cts/GameManagerTest.java
rename to tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java
diff --git a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
index e2c92dc..ecdf49d 100644
--- a/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
+++ b/tests/tests/graphics/jni/VulkanPreTransformTestHelpers.cpp
@@ -69,10 +69,6 @@
"VK_KHR_swapchain",
};
-static const char* blacklistedDeviceExtensions[] = {
- "VK_KHR_performance_query",
-};
-
static bool enumerateInstanceExtensions(std::vector<VkExtensionProperties>* extensions) {
VkResult result;
@@ -195,11 +191,6 @@
std::vector<VkExtensionProperties> supportedDeviceExtensions;
ASSERT(enumerateDeviceExtensions(mGpu, &supportedDeviceExtensions));
- // Fail if the blacklisted extensions are advertised as supported
- for (const auto extension : blacklistedDeviceExtensions) {
- ASSERT(!hasExtension(extension, supportedDeviceExtensions));
- }
-
std::vector<const char*> enabledDeviceExtensions;
for (const auto extension : requiredDeviceExtensions) {
ASSERT(hasExtension(extension, supportedDeviceExtensions));
diff --git a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
index 70e0735..ccfb722 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ColorTest.java
@@ -76,42 +76,66 @@
};
int systemColors[] = {
- android.R.color.system_primary_0,
- android.R.color.system_primary_50,
- android.R.color.system_primary_100,
- android.R.color.system_primary_200,
- android.R.color.system_primary_300,
- android.R.color.system_primary_400,
- android.R.color.system_primary_500,
- android.R.color.system_primary_600,
- android.R.color.system_primary_700,
- android.R.color.system_primary_800,
- android.R.color.system_primary_900,
- android.R.color.system_primary_1000,
- android.R.color.system_secondary_0,
- android.R.color.system_secondary_50,
- android.R.color.system_secondary_100,
- android.R.color.system_secondary_200,
- android.R.color.system_secondary_300,
- android.R.color.system_secondary_400,
- android.R.color.system_secondary_500,
- android.R.color.system_secondary_600,
- android.R.color.system_secondary_700,
- android.R.color.system_secondary_800,
- android.R.color.system_secondary_900,
- android.R.color.system_secondary_1000,
- android.R.color.system_neutral_0,
- android.R.color.system_neutral_50,
- android.R.color.system_neutral_100,
- android.R.color.system_neutral_200,
- android.R.color.system_neutral_300,
- android.R.color.system_neutral_400,
- android.R.color.system_neutral_500,
- android.R.color.system_neutral_600,
- android.R.color.system_neutral_700,
- android.R.color.system_neutral_800,
- android.R.color.system_neutral_900,
- android.R.color.system_neutral_1000,
+ android.R.color.system_neutral1_0,
+ android.R.color.system_neutral1_50,
+ android.R.color.system_neutral1_100,
+ android.R.color.system_neutral1_200,
+ android.R.color.system_neutral1_300,
+ android.R.color.system_neutral1_400,
+ android.R.color.system_neutral1_500,
+ android.R.color.system_neutral1_600,
+ android.R.color.system_neutral1_700,
+ android.R.color.system_neutral1_800,
+ android.R.color.system_neutral1_900,
+ android.R.color.system_neutral1_1000,
+ android.R.color.system_neutral2_0,
+ android.R.color.system_neutral2_50,
+ android.R.color.system_neutral2_100,
+ android.R.color.system_neutral2_200,
+ android.R.color.system_neutral2_300,
+ android.R.color.system_neutral2_400,
+ android.R.color.system_neutral2_500,
+ android.R.color.system_neutral2_600,
+ android.R.color.system_neutral2_700,
+ android.R.color.system_neutral2_800,
+ android.R.color.system_neutral2_900,
+ android.R.color.system_neutral2_1000,
+ android.R.color.system_accent1_0,
+ android.R.color.system_accent1_50,
+ android.R.color.system_accent1_100,
+ android.R.color.system_accent1_200,
+ android.R.color.system_accent1_300,
+ android.R.color.system_accent1_400,
+ android.R.color.system_accent1_500,
+ android.R.color.system_accent1_600,
+ android.R.color.system_accent1_700,
+ android.R.color.system_accent1_800,
+ android.R.color.system_accent1_900,
+ android.R.color.system_accent1_1000,
+ android.R.color.system_accent2_0,
+ android.R.color.system_accent2_50,
+ android.R.color.system_accent2_100,
+ android.R.color.system_accent2_200,
+ android.R.color.system_accent2_300,
+ android.R.color.system_accent2_400,
+ android.R.color.system_accent2_500,
+ android.R.color.system_accent2_600,
+ android.R.color.system_accent2_700,
+ android.R.color.system_accent2_800,
+ android.R.color.system_accent2_900,
+ android.R.color.system_accent2_1000,
+ android.R.color.system_accent3_0,
+ android.R.color.system_accent3_50,
+ android.R.color.system_accent3_100,
+ android.R.color.system_accent3_200,
+ android.R.color.system_accent3_300,
+ android.R.color.system_accent3_400,
+ android.R.color.system_accent3_500,
+ android.R.color.system_accent3_600,
+ android.R.color.system_accent3_700,
+ android.R.color.system_accent3_800,
+ android.R.color.system_accent3_900,
+ android.R.color.system_accent3_1000,
};
List<Integer> expectedColorStateLists = Arrays.asList(
diff --git a/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
index 5e0d4f6..d9dded5 100644
--- a/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
+++ b/tests/tests/graphics/src/android/graphics/cts/SystemPalette.java
@@ -41,101 +41,86 @@
@RunWith(AndroidJUnit4.class)
public class SystemPalette {
- private static final double MAX_CHROMA_DISTANCE = 0.1;
- private static final String LOG_TAG = SystemPalette.class.getSimpleName();
+ // Hue goes from 0 to 360
+ private static final int MAX_HUE_DISTANCE = 30;
@Test
public void testShades0and1000() {
final Context context = getInstrumentation().getTargetContext();
- final int primary0 = context.getColor(R.color.system_primary_0);
- final int primary1000 = context.getColor(R.color.system_primary_1000);
- final int secondary0 = context.getColor(R.color.system_secondary_0);
- final int secondary1000 = context.getColor(R.color.system_secondary_1000);
- final int neutral0 = context.getColor(R.color.system_neutral_0);
- final int neutral1000 = context.getColor(R.color.system_neutral_1000);
- assertColor(primary0, Color.WHITE);
- assertColor(primary1000, Color.BLACK);
- assertColor(secondary0, Color.WHITE);
- assertColor(secondary1000, Color.BLACK);
- assertColor(neutral0, Color.WHITE);
- assertColor(neutral1000, Color.BLACK);
+ final int[] leftmostColors = new int[]{
+ R.color.system_neutral1_0, R.color.system_neutral2_0, R.color.system_accent1_0,
+ R.color.system_accent2_0, R.color.system_accent3_0
+ };
+ final int[] rightmostColors = new int[]{
+ R.color.system_neutral1_1000, R.color.system_neutral2_1000,
+ R.color.system_accent1_1000, R.color.system_accent2_1000,
+ R.color.system_accent3_1000
+ };
+ for (int i = 0; i < leftmostColors.length; i++) {
+ assertColor(context.getColor(leftmostColors[0]), Color.WHITE);
+ }
+ for (int i = 0; i < rightmostColors.length; i++) {
+ assertColor(context.getColor(rightmostColors[0]), Color.BLACK);
+ }
}
@Test
public void testAllColorsBelongToSameFamily() {
final Context context = getInstrumentation().getTargetContext();
- final int[] primaryColors = getAllPrimaryColors(context);
- final int[] secondaryColors = getAllSecondaryColors(context);
- final int[] neutralColors = getAllNeutralColors(context);
+ List<int[]> allPalettes = Arrays.asList(getAllAccent1Colors(context),
+ getAllAccent2Colors(context), getAllAccent3Colors(context),
+ getAllNeutral1Colors(context), getAllNeutral2Colors(context));
- for (int i = 2; i < primaryColors.length - 1; i++) {
- assertWithMessage("Primary color " + Integer.toHexString((primaryColors[i - 1]))
- + " has different chroma compared to " + Integer.toHexString(primaryColors[i]))
- .that(similarChroma(primaryColors[i - 1], primaryColors[i])).isTrue();
- assertWithMessage("Secondary color " + Integer.toHexString((secondaryColors[i - 1]))
- + " has different chroma compared to " + Integer.toHexString(
- secondaryColors[i]))
- .that(similarChroma(secondaryColors[i - 1], secondaryColors[i])).isTrue();
- assertWithMessage("Neutral color " + Integer.toHexString((neutralColors[i - 1]))
- + " has different chroma compared to " + Integer.toHexString(neutralColors[i]))
- .that(similarChroma(neutralColors[i - 1], neutralColors[i])).isTrue();
+ for (int[] palette : allPalettes) {
+ for (int i = 2; i < palette.length - 1; i++) {
+ assertWithMessage("Color " + Integer.toHexString((palette[i - 1]))
+ + " has different chroma compared to " + Integer.toHexString(palette[i])
+ + " for palette: " + Arrays.toString(palette))
+ .that(similarHue(palette[i - 1], palette[i])).isTrue();
+ }
}
}
/**
- * Compare if color A and B have similar color, in LAB space.
+ * Compare if color A and B have similar hue, in HSL space.
*
* @param colorA Color 1
* @param colorB Color 2
* @return True when colors have similar chroma.
*/
- private boolean similarChroma(@ColorInt int colorA, @ColorInt int colorB) {
- final double[] labColor1 = new double[3];
- final double[] labColor2 = new double[3];
+ private boolean similarHue(@ColorInt int colorA, @ColorInt int colorB) {
+ final float[] hslColor1 = new float[3];
+ final float[] hslColor2 = new float[3];
- ColorUtils.RGBToLAB(Color.red(colorA), Color.green(colorA), Color.blue(colorA), labColor1);
- ColorUtils.RGBToLAB(Color.red(colorB), Color.green(colorB), Color.blue(colorB), labColor2);
+ ColorUtils.RGBToHSL(Color.red(colorA), Color.green(colorA), Color.blue(colorA), hslColor1);
+ ColorUtils.RGBToHSL(Color.red(colorB), Color.green(colorB), Color.blue(colorB), hslColor2);
- labColor1[1] = (labColor1[1] + 128.0) / 256;
- labColor1[2] = (labColor1[2] + 128.0) / 256;
- labColor2[1] = (labColor2[1] + 128.0) / 256;
- labColor2[2] = (labColor2[2] + 128.0) / 256;
+ float hue1 = Math.max(hslColor1[0], hslColor2[0]);
+ float hue2 = Math.min(hslColor1[0], hslColor2[0]);
- return (Math.abs(labColor1[1] - labColor2[1]) < MAX_CHROMA_DISTANCE)
- && (Math.abs(labColor1[2] - labColor2[2]) < MAX_CHROMA_DISTANCE);
+ return hue1 - hue2 < MAX_HUE_DISTANCE;
}
@Test
public void testColorsMatchExpectedLuminosity() {
final Context context = getInstrumentation().getTargetContext();
- final int[] primaryColors = getAllPrimaryColors(context);
- final int[] secondaryColors = getAllSecondaryColors(context);
- final int[] neutralColors = getAllNeutralColors(context);
+ List<int[]> allPalettes = Arrays.asList(getAllAccent1Colors(context),
+ getAllAccent2Colors(context), getAllAccent3Colors(context),
+ getAllNeutral1Colors(context), getAllNeutral2Colors(context));
- final double[] labPrimary = new double[3];
- final double[] labSecondary = new double[3];
- final double[] labNeutral = new double[3];
+ final double[] labColor = new double[3];
final double[] expectedL = {100, 95, 90, 80, 70, 60, 49, 40, 30, 20, 10, 0};
- for (int i = 0; i < primaryColors.length; i++) {
- ColorUtils.RGBToLAB(Color.red(primaryColors[i]), Color.green(primaryColors[i]),
- Color.blue(primaryColors[i]), labPrimary);
- ColorUtils.RGBToLAB(Color.red(secondaryColors[i]), Color.green(secondaryColors[i]),
- Color.blue(secondaryColors[i]), labSecondary);
- ColorUtils.RGBToLAB(Color.red(neutralColors[i]), Color.green(neutralColors[i]),
- Color.blue(neutralColors[i]), labNeutral);
+ for (int[] palette : allPalettes) {
+ for (int i = 0; i < palette.length; i++) {
+ ColorUtils.colorToLAB(palette[i], labColor);
- // Colors in the same palette should vary mostly in L, decreasing lightness as we move
- // across the palette.
- assertWithMessage("Color " + Integer.toHexString((primaryColors[i]))
- + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
- .that(labPrimary[0]).isWithin(3).of(expectedL[i]);
- assertWithMessage("Color " + Integer.toHexString((secondaryColors[i]))
- + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
- .that(labSecondary[0]).isWithin(3).of(expectedL[i]);
- assertWithMessage("Color " + Integer.toHexString((neutralColors[i]))
- + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
- .that(labNeutral[0]).isWithin(3).of(expectedL[i]);
+ // Colors in the same palette should vary mostly in L, decreasing lightness as we
+ // move across the palette.
+ assertWithMessage("Color " + Integer.toHexString((palette[i]))
+ + " at index " + i + " should have L " + expectedL[i] + " in LAB space.")
+ .that(labColor[0]).isWithin(3).of(expectedL[i]);
+ }
}
}
@@ -151,12 +136,12 @@
new Pair<>(300, 700), new Pair<>(400, 800), new Pair<>(500, 900),
new Pair<>(600, 1000));
- final int[] primaryColors = getAllPrimaryColors(context);
- final int[] secondaryColors = getAllSecondaryColors(context);
- final int[] neutralColors = getAllNeutralColors(context);
+ List<int[]> allPalettes = Arrays.asList(getAllAccent1Colors(context),
+ getAllAccent2Colors(context), getAllAccent3Colors(context),
+ getAllNeutral1Colors(context), getAllNeutral2Colors(context));
- for (int[] palette: Arrays.asList(primaryColors, secondaryColors, neutralColors)) {
- for (Pair<Integer, Integer> shades: atLeast4dot5) {
+ for (int[] palette : allPalettes) {
+ for (Pair<Integer, Integer> shades : atLeast4dot5) {
final int background = palette[shadeToArrayIndex(shades.first)];
final int foreground = palette[shadeToArrayIndex(shades.second)];
final double contrast = ColorUtils.calculateContrast(foreground, background);
@@ -166,7 +151,7 @@
.isGreaterThan(4.5);
}
- for (Pair<Integer, Integer> shades: atLeast3dot0) {
+ for (Pair<Integer, Integer> shades : atLeast3dot0) {
final int background = palette[shadeToArrayIndex(shades.first)];
final int foreground = palette[shadeToArrayIndex(shades.second)];
final double contrast = ColorUtils.calculateContrast(foreground, background);
@@ -183,9 +168,8 @@
*
* @param shade Shade from 0 to 1000.
* @return index in array
- * @see #getAllPrimaryColors(Context)
- * @see #getAllSecondaryColors(Context)
- * @see #getAllNeutralColors(Context)
+ * @see #getAllAccent1Colors(Context) (Context)
+ * @see #getAllNeutral1Colors(Context)
*/
private int shadeToArrayIndex(int shade) {
if (shade == 0) {
@@ -199,58 +183,91 @@
private void assertColor(@ColorInt int observed, @ColorInt int expected) {
Assert.assertEquals("Color = " + Integer.toHexString(observed) + ", "
- + Integer.toHexString(expected) + " expected",
- observed, expected);
+ + Integer.toHexString(expected) + " expected", expected, observed);
}
- private int[] getAllPrimaryColors(Context context) {
+ private int[] getAllAccent1Colors(Context context) {
final int[] colors = new int[12];
- colors[0] = context.getColor(R.color.system_primary_0);
- colors[1] = context.getColor(R.color.system_primary_50);
- colors[2] = context.getColor(R.color.system_primary_100);
- colors[3] = context.getColor(R.color.system_primary_200);
- colors[4] = context.getColor(R.color.system_primary_300);
- colors[5] = context.getColor(R.color.system_primary_400);
- colors[6] = context.getColor(R.color.system_primary_500);
- colors[7] = context.getColor(R.color.system_primary_600);
- colors[8] = context.getColor(R.color.system_primary_700);
- colors[9] = context.getColor(R.color.system_primary_800);
- colors[10] = context.getColor(R.color.system_primary_900);
- colors[11] = context.getColor(R.color.system_primary_1000);
+ colors[0] = context.getColor(R.color.system_accent1_0);
+ colors[1] = context.getColor(R.color.system_accent1_50);
+ colors[2] = context.getColor(R.color.system_accent1_100);
+ colors[3] = context.getColor(R.color.system_accent1_200);
+ colors[4] = context.getColor(R.color.system_accent1_300);
+ colors[5] = context.getColor(R.color.system_accent1_400);
+ colors[6] = context.getColor(R.color.system_accent1_500);
+ colors[7] = context.getColor(R.color.system_accent1_600);
+ colors[8] = context.getColor(R.color.system_accent1_700);
+ colors[9] = context.getColor(R.color.system_accent1_800);
+ colors[10] = context.getColor(R.color.system_accent1_900);
+ colors[11] = context.getColor(R.color.system_accent1_1000);
return colors;
}
- private int[] getAllSecondaryColors(Context context) {
+ private int[] getAllAccent2Colors(Context context) {
final int[] colors = new int[12];
- colors[0] = context.getColor(R.color.system_secondary_0);
- colors[1] = context.getColor(R.color.system_secondary_50);
- colors[2] = context.getColor(R.color.system_secondary_100);
- colors[3] = context.getColor(R.color.system_secondary_200);
- colors[4] = context.getColor(R.color.system_secondary_300);
- colors[5] = context.getColor(R.color.system_secondary_400);
- colors[6] = context.getColor(R.color.system_secondary_500);
- colors[7] = context.getColor(R.color.system_secondary_600);
- colors[8] = context.getColor(R.color.system_secondary_700);
- colors[9] = context.getColor(R.color.system_secondary_800);
- colors[10] = context.getColor(R.color.system_secondary_900);
- colors[11] = context.getColor(R.color.system_secondary_1000);
+ colors[0] = context.getColor(R.color.system_accent2_0);
+ colors[1] = context.getColor(R.color.system_accent2_50);
+ colors[2] = context.getColor(R.color.system_accent2_100);
+ colors[3] = context.getColor(R.color.system_accent2_200);
+ colors[4] = context.getColor(R.color.system_accent2_300);
+ colors[5] = context.getColor(R.color.system_accent2_400);
+ colors[6] = context.getColor(R.color.system_accent2_500);
+ colors[7] = context.getColor(R.color.system_accent2_600);
+ colors[8] = context.getColor(R.color.system_accent2_700);
+ colors[9] = context.getColor(R.color.system_accent2_800);
+ colors[10] = context.getColor(R.color.system_accent2_900);
+ colors[11] = context.getColor(R.color.system_accent2_1000);
return colors;
}
- private int[] getAllNeutralColors(Context context) {
+ private int[] getAllAccent3Colors(Context context) {
final int[] colors = new int[12];
- colors[0] = context.getColor(R.color.system_neutral_0);
- colors[1] = context.getColor(R.color.system_neutral_50);
- colors[2] = context.getColor(R.color.system_neutral_100);
- colors[3] = context.getColor(R.color.system_neutral_200);
- colors[4] = context.getColor(R.color.system_neutral_300);
- colors[5] = context.getColor(R.color.system_neutral_400);
- colors[6] = context.getColor(R.color.system_neutral_500);
- colors[7] = context.getColor(R.color.system_neutral_600);
- colors[8] = context.getColor(R.color.system_neutral_700);
- colors[9] = context.getColor(R.color.system_neutral_800);
- colors[10] = context.getColor(R.color.system_neutral_900);
- colors[11] = context.getColor(R.color.system_neutral_1000);
+ colors[0] = context.getColor(R.color.system_accent3_0);
+ colors[1] = context.getColor(R.color.system_accent3_50);
+ colors[2] = context.getColor(R.color.system_accent3_100);
+ colors[3] = context.getColor(R.color.system_accent3_200);
+ colors[4] = context.getColor(R.color.system_accent3_300);
+ colors[5] = context.getColor(R.color.system_accent3_400);
+ colors[6] = context.getColor(R.color.system_accent3_500);
+ colors[7] = context.getColor(R.color.system_accent3_600);
+ colors[8] = context.getColor(R.color.system_accent3_700);
+ colors[9] = context.getColor(R.color.system_accent3_800);
+ colors[10] = context.getColor(R.color.system_accent3_900);
+ colors[11] = context.getColor(R.color.system_accent3_1000);
+ return colors;
+ }
+
+ private int[] getAllNeutral1Colors(Context context) {
+ final int[] colors = new int[12];
+ colors[0] = context.getColor(R.color.system_neutral1_0);
+ colors[1] = context.getColor(R.color.system_neutral1_50);
+ colors[2] = context.getColor(R.color.system_neutral1_100);
+ colors[3] = context.getColor(R.color.system_neutral1_200);
+ colors[4] = context.getColor(R.color.system_neutral1_300);
+ colors[5] = context.getColor(R.color.system_neutral1_400);
+ colors[6] = context.getColor(R.color.system_neutral1_500);
+ colors[7] = context.getColor(R.color.system_neutral1_600);
+ colors[8] = context.getColor(R.color.system_neutral1_700);
+ colors[9] = context.getColor(R.color.system_neutral1_800);
+ colors[10] = context.getColor(R.color.system_neutral1_900);
+ colors[11] = context.getColor(R.color.system_neutral1_1000);
+ return colors;
+ }
+
+ private int[] getAllNeutral2Colors(Context context) {
+ final int[] colors = new int[12];
+ colors[0] = context.getColor(R.color.system_neutral2_0);
+ colors[1] = context.getColor(R.color.system_neutral2_50);
+ colors[2] = context.getColor(R.color.system_neutral2_100);
+ colors[3] = context.getColor(R.color.system_neutral2_200);
+ colors[4] = context.getColor(R.color.system_neutral2_300);
+ colors[5] = context.getColor(R.color.system_neutral2_400);
+ colors[6] = context.getColor(R.color.system_neutral2_500);
+ colors[7] = context.getColor(R.color.system_neutral2_600);
+ colors[8] = context.getColor(R.color.system_neutral2_700);
+ colors[9] = context.getColor(R.color.system_neutral2_800);
+ colors[10] = context.getColor(R.color.system_neutral2_900);
+ colors[11] = context.getColor(R.color.system_neutral2_1000);
return colors;
}
}
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
index 88e94e4..8b26b5b 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
@@ -61,6 +61,7 @@
private static final int VULKAN_1_0 = 0x00400003; // 1.0.3
private static final int VULKAN_1_1 = 0x00401000; // 1.1.0
+ private static final String VK_KHR_PERFORMANCE_QUERY = "VK_KHR_performance_query";
private static final String VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME =
"VK_ANDROID_external_memory_android_hardware_buffer";
private static final int VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_SPEC_VERSION = 2;
@@ -223,6 +224,16 @@
mVulkanHardwareLevel != null && mVulkanHardwareLevel.version >= 0);
}
+ @Test
+ public void testVulkanBlockedExtensions() throws JSONException {
+ for (JSONObject device : mVulkanDevices) {
+ assertTrue("Device - " + device.getJSONObject("properties").getString("deviceName")
+ + " supports extension " + VK_KHR_PERFORMANCE_QUERY
+ + ". It is blocked and hence should not be supported",
+ !hasExtension(device, VK_KHR_PERFORMANCE_QUERY, 0));
+ }
+ }
+
private JSONObject getBestDevice() throws JSONException {
JSONObject bestDevice = null;
int bestDeviceLevel = -1;
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java b/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
index 9fa5fd5..5a20570 100644
--- a/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
+++ b/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
@@ -582,4 +582,20 @@
assertEquals(50.0f, r.getLineWidth(1), 0.0f);
assertEquals(70.0f, r.getLineWidth(2), 0.0f);
}
+
+ @Test
+ public void testLineBreak_ZeroWidthTab() {
+ final String text = "Hi, \tWorld.";
+ final LineBreaker lb = new LineBreaker.Builder()
+ .setBreakStrategy(BREAK_STRATEGY_SIMPLE)
+ .build();
+ final ParagraphConstraints c = new ParagraphConstraints();
+ c.setWidth(70.0f);
+ c.setTabStops(null, 0);
+ Result r = lb.computeLineBreaks(new MeasuredText.Builder(text.toCharArray())
+ .appendStyleRun(sPaint, text.length(), false)
+ .build(), c, 0);
+ float lw = r.getLineWidth(0);
+ assertFalse(Float.isNaN(lw));
+ }
}
diff --git a/tests/tests/icu/CtsIcu4cTestCases.xml b/tests/tests/icu/CtsIcu4cTestCases.xml
index c216b3a..3ef1629 100644
--- a/tests/tests/icu/CtsIcu4cTestCases.xml
+++ b/tests/tests/icu/CtsIcu4cTestCases.xml
@@ -36,8 +36,8 @@
<option name="push" value="icu4c_test_data.zip->/data/local/tmp/icu4c_test_data.zip" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command" value="mkdir -p /data/local/tmp/test/testdata && unzip -o -d /data/local/tmp/test/testdata /data/local/tmp/icu4c_test_data.zip" />
- <option name="teardown-command" value="rm -r /data/local/tmp/test/testdata" />
+ <option name="run-command" value="unzip -o -d /data/local/tmp/ /data/local/tmp/icu4c_test_data.zip" />
+ <option name="teardown-command" value="rm -r /data/local/tmp/test /data/local/tmp/data" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/icu/resources/android/icu/cts/expectations/icu-known-failures.txt b/tests/tests/icu/resources/android/icu/cts/expectations/icu-known-failures.txt
index e68fcd9..1b1535a 100644
--- a/tests/tests/icu/resources/android/icu/cts/expectations/icu-known-failures.txt
+++ b/tests/tests/icu/resources/android/icu/cts/expectations/icu-known-failures.txt
@@ -2,14 +2,6 @@
* This file contains expectations for tests that are known to fail.
*/
[
-// Uncomment this to exclude all tests and then add result: "SUCCESS" to any specific tests that
-// need to be run.
-/*
-{
- description: "Exclude all tests",
- name: "android.icu.dev.test"
-},
-*/
{
description: "Serialized forms have not been converted to use repackaged classes",
name: "android.icu.dev.test.format.NumberFormatRegressionTest#TestSerialization",
@@ -19,22 +11,5 @@
description: "Checks differences in DecimalFormat classes from ICU4J and JDK but on Android java.text.DecimalFormat is implemented in terms of ICU4J",
name: "android.icu.dev.test.format.NumberFormatTest#TestDataDrivenJDK",
bug: "27711713"
-},
-{
- description: "Collation rules data has been removed from ICU4J data on Android",
- names: [
- "android.icu.dev.test.collator.CollationCreationMethodTest#TestRuleVsLocaleCreationMonkey",
- "android.icu.dev.test.collator.CollationMiscTest#TestImport",
- "android.icu.dev.test.collator.CollationMiscTest#TestImportWithType",
- "android.icu.dev.test.collator.CollationMiscTest#TestUCARules",
- "android.icu.dev.test.collator.CollationTest#TestDataDriven",
- "android.icu.dev.test.collator.G7CollationTest#TestG7Data"
- ],
- bug: "27552651"
-},
-{
- description: "Unknown Language != Unknown language",
- name: "android.icu.dev.test.TestLocaleNamePackaging#testLanguageDisplayNames",
- bug: "33447162"
}
]
diff --git a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
index 08267cc..796fbd8 100644
--- a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
+++ b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
@@ -82,7 +82,8 @@
private final static String[] PUBLIC_ART_LIBRARIES = {
"libicui18n.so",
"libicuuc.so",
- "libnativehelper.so"
+ "libnativehelper.so",
+ "libsigchain.so"
};
// The grey-list.
diff --git a/tests/tests/media/DynamicConfig.xml b/tests/tests/media/DynamicConfig.xml
index 4548620..942ab80 100644
--- a/tests/tests/media/DynamicConfig.xml
+++ b/tests/tests/media/DynamicConfig.xml
@@ -14,12 +14,6 @@
-->
<dynamicConfig>
- <entry key="decoder_test_audio_url">
- <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&key=ik0&user=android-device-test</value>
- </entry>
- <entry key="decoder_test_video_url">
- <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000&sparams=ip,ipbits,expire,id,itag,source&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&key=ik0&user=android-device-test</value>
- </entry>
<entry key="media_codec_capabilities_test_avc_baseline12">
<value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&itag=160&source=youtube&user=android-device-test&sparams=ip,ipbits,expire,id,itag,source,user&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD.702DE9BA7AF96785FD6930AD2DD693A0486C880E&key=ik0</value>
</entry>
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index 8b768ee..0c7fa25 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -44,6 +44,7 @@
import static android.media.AudioManager.VIBRATE_TYPE_RINGER;
import static android.provider.Settings.System.SOUND_EFFECTS_ENABLED;
+import android.Manifest;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
@@ -55,6 +56,7 @@
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioProfile;
import android.media.MediaPlayer;
@@ -72,11 +74,16 @@
import android.util.Log;
import android.view.SoundEffectConstants;
+import androidx.test.InstrumentationRegistry;
+
import com.android.compatibility.common.util.ApiLevelUtil;
import com.android.compatibility.common.util.CddTest;
import com.android.compatibility.common.util.MediaUtils;
+import com.android.compatibility.common.util.SettingsStateKeeperRule;
import com.android.internal.annotations.GuardedBy;
+import org.junit.ClassRule;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -120,6 +127,16 @@
private boolean mDoNotCheckUnmute;
private boolean mAppsBypassingDnd;
+ @ClassRule
+ public static final SettingsStateKeeperRule mSurroundSoundFormatsSettingsKeeper =
+ new SettingsStateKeeperRule(InstrumentationRegistry.getTargetContext(),
+ Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS);
+
+ @ClassRule
+ public static final SettingsStateKeeperRule mSurroundSoundModeSettingsKeeper =
+ new SettingsStateKeeperRule(InstrumentationRegistry.getTargetContext(),
+ Settings.Global.ENCODED_SURROUND_OUTPUT);
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -367,6 +384,36 @@
assertEquals(MODE_NORMAL, mAudioManager.getMode());
}
+ public void testSetSurroundFormatEnabled() throws Exception {
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.WRITE_SETTINGS);
+
+ int audioFormat = AudioFormat.ENCODING_DTS;
+
+ mAudioManager.setSurroundFormatEnabled(audioFormat, true /*enabled*/);
+ assertTrue(mAudioManager.isSurroundFormatEnabled(audioFormat));
+
+ mAudioManager.setSurroundFormatEnabled(audioFormat, false /*enabled*/);
+ assertFalse(mAudioManager.isSurroundFormatEnabled(audioFormat));
+
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ }
+
+ public void testSetEncodedSurroundMode() throws Exception {
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.WRITE_SETTINGS);
+
+ int expectedSurroundFormatsMode = Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL;
+ mAudioManager.setEncodedSurroundMode(expectedSurroundFormatsMode);
+ assertEquals(expectedSurroundFormatsMode, mAudioManager.getEncodedSurroundMode());
+
+ expectedSurroundFormatsMode = Settings.Global.ENCODED_SURROUND_OUTPUT_NEVER;
+ mAudioManager.setEncodedSurroundMode(expectedSurroundFormatsMode);
+ assertEquals(expectedSurroundFormatsMode, mAudioManager.getEncodedSurroundMode());
+
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ }
+
@SuppressWarnings("deprecation")
@AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
public void testRouting() throws Exception {
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
index f939b70..e54cf71 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
@@ -50,6 +50,9 @@
static final String mInpPrefix = WorkDir.getMediaDirString();
private final static int TEST_TIMING_TOLERANCE_MS = 150;
+ /** acceptable timeout for the time it takes for a prepared MediaPlayer to have an audio device
+ * selected and reported when starting to play */
+ private final static int PLAY_ROUTING_TIMING_TOLERANCE_MS = 500;
private final static int TEST_TIMEOUT_SOUNDPOOL_LOAD_MS = 3000;
private final static long MEDIAPLAYER_PREPARE_TIMEOUT_MS = 2000;
@@ -444,7 +447,8 @@
mp.start();
assertTrue("onPlaybackConfigChanged play and device called expected "
- , callback.waitForCallbacks(2));
+ , callback.waitForCallbacks(2,
+ TEST_TIMING_TOLERANCE_MS + PLAY_ROUTING_TIMING_TOLERANCE_MS));
assertEquals("number of active players not expected",
// one more player active
activePlayerCount/*expected*/, callback.getNbConfigs());
@@ -496,10 +500,10 @@
mOnCalledMonitor.signal();
}
- public boolean waitForCallbacks(int calledCount) throws InterruptedException {
+ public boolean waitForCallbacks(int calledCount, long timeoutMs)
+ throws InterruptedException {
int signalsCounted =
- mOnCalledMonitor.waitForCountedSignals(calledCount,
- calledCount*TEST_TIMING_TOLERANCE_MS);
+ mOnCalledMonitor.waitForCountedSignals(calledCount, timeoutMs);
return (signalsCounted == calledCount);
}
}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index 2ccf389..f080be7 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -39,6 +39,7 @@
import android.media.MicrophoneDirection;
import android.media.MicrophoneInfo;
import android.media.cts.AudioRecordingConfigurationTest.MyAudioRecordingCallback;
+import android.media.metrics.LogSessionId;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -1822,8 +1823,9 @@
.setChannelMask(AudioFormat.CHANNEL_IN_MONO)
.build())
.build();
- audioRecord.setLogSessionId(null); // should not throw.
- audioRecord.setLogSessionId("0123456789abcdef"); // 16 char Base64Url id.
+ audioRecord.setLogSessionId(LogSessionId.LOG_SESSION_ID_NONE); // should not throw.
+ audioRecord.setLogSessionId(
+ new LogSessionId("0123456789abcdef")); // 16 char Base64Url id.
// record some data to generate a log entry.
short data[] = new short[audioRecord.getSampleRate() / 2];
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 7b97fc6..82366ae 100755
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -35,6 +35,7 @@
import android.media.AudioTimestamp;
import android.media.AudioTrack;
import android.media.PlaybackParams;
+import android.media.metrics.LogSessionId;
import android.os.PersistableBundle;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
@@ -2943,8 +2944,9 @@
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build())
.build();
- audioTrack.setLogSessionId(null); // should not throw.
- audioTrack.setLogSessionId("0123456789abcdef"); // 16 char Base64Url id.
+ audioTrack.setLogSessionId(LogSessionId.LOG_SESSION_ID_NONE); // should not throw.
+ audioTrack.setLogSessionId(
+ new LogSessionId("0123456789abcdef")); // 16 char Base64Url id.
// write some data to generate a log entry.
short data[] = new short[audioTrack.getSampleRate() / 2];
@@ -2961,6 +2963,241 @@
}
}
+ /*
+ * The following helpers and tests are used to test setting
+ * and getting the start threshold in frames.
+ *
+ * See Android CDD 5.6 [C-1-2] Cold output latency
+ */
+ private static final int START_THRESHOLD_SLEEP_MILLIS = 500;
+
+ /**
+ * Helper test that validates setting the start threshold.
+ *
+ * @param track
+ * @param startThresholdInFrames
+ * @throws Exception
+ */
+ private static void validateSetStartThresholdInFrames(
+ AudioTrack track, int startThresholdInFrames) throws Exception {
+ assertEquals(startThresholdInFrames,
+ track.setStartThresholdInFrames(startThresholdInFrames));
+ assertEquals(startThresholdInFrames,
+ track.getStartThresholdInFrames());
+ }
+
+ /**
+ * Helper that tests that the head position eventually equals expectedFrames.
+ *
+ * Exponential backoff to ~ 2 x START_THRESHOLD_SLEEP_MILLIS
+ *
+ * @param track
+ * @param expectedFrames
+ * @param message
+ * @throws Exception
+ */
+ private static void validatePlaybackHeadPosition(
+ AudioTrack track, int expectedFrames, String message) throws Exception {
+ int cumulativeMillis = 0;
+ int playbackHeadPosition = 0;
+ for (double testMillis = START_THRESHOLD_SLEEP_MILLIS * 0.125;
+ testMillis <= START_THRESHOLD_SLEEP_MILLIS; // this is exact for IEEE binary double
+ testMillis *= 2.) {
+ Thread.sleep((int)testMillis);
+ playbackHeadPosition = track.getPlaybackHeadPosition();
+ if (playbackHeadPosition == expectedFrames) return;
+ cumulativeMillis += (int)testMillis;
+ }
+ fail(message + ": expected track playbackHeadPosition: " + expectedFrames
+ + " actual playbackHeadPosition: " + playbackHeadPosition
+ + " wait time: " + cumulativeMillis + "ms");
+ }
+
+ /**
+ * Helper test that sets the start threshold to frames, and validates
+ * writing exactly frames amount of data is needed to start the
+ * track streaming.
+ *
+ * @param track
+ * @param frames
+ * @throws Exception
+ */
+ private static void validateWriteStartsStream(
+ AudioTrack track, int frames) throws Exception {
+ assertEquals(1, track.getChannelCount()); // must be MONO
+ final short[] data = new short[frames];
+
+ // The track must be idle/underrun or the test will fail.
+ int expectedFrames = track.getPlaybackHeadPosition();
+
+ // Set our threshold to frames.
+ validateSetStartThresholdInFrames(track, frames);
+
+ Thread.sleep(START_THRESHOLD_SLEEP_MILLIS);
+ assertEquals("Changing start threshold doesn't start if it is larger than buffer data",
+ expectedFrames, track.getPlaybackHeadPosition());
+
+ // Write a small amount of data, this isn't enough to start the track.
+ final int PARTIAL_WRITE_IN_FRAMES = frames - 1;
+ track.write(data, 0 /* offsetInShorts */, PARTIAL_WRITE_IN_FRAMES);
+
+ // Ensure the track hasn't started.
+ Thread.sleep(START_THRESHOLD_SLEEP_MILLIS);
+ assertEquals("Track needs enough frames to start",
+ expectedFrames, track.getPlaybackHeadPosition());
+
+ // Write exactly threshold frames out, this should kick the playback off.
+ track.write(data, 0 /* offsetInShorts */, data.length - PARTIAL_WRITE_IN_FRAMES);
+
+ // Verify that we have processed the data now.
+ expectedFrames += frames;
+ Thread.sleep(frames * 1000L / track.getSampleRate()); // accommodate for #frames.
+ validatePlaybackHeadPosition(track, expectedFrames,
+ "Writing buffer data to start threshold should start streaming");
+ }
+
+ /**
+ * Helper that tests reducing the start threshold to frames will start track
+ * streaming when frames of data are written to it. (Presumes the
+ * previous start threshold was greater than frames).
+ *
+ * @param track
+ * @param frames
+ * @throws Exception
+ */
+ private static void validateSetStartThresholdStartsStream(
+ AudioTrack track, int frames) throws Exception {
+ assertTrue(track.getStartThresholdInFrames() > frames);
+ assertEquals(1, track.getChannelCount()); // must be MONO
+ final short[] data = new short[frames];
+
+ // The track must be idle/underrun or the test will fail.
+ int expectedFrames = track.getPlaybackHeadPosition();
+
+ // This write is too small for now.
+ track.write(data, 0 /* offsetInShorts */, data.length);
+
+ Thread.sleep(START_THRESHOLD_SLEEP_MILLIS);
+ assertEquals("Track needs enough frames to start",
+ expectedFrames, track.getPlaybackHeadPosition());
+
+ // Reduce our start threshold. This should start streaming.
+ validateSetStartThresholdInFrames(track, frames);
+
+ // Verify that we have processed the data now.
+ expectedFrames += frames;
+ Thread.sleep(frames * 1000L / track.getSampleRate()); // accommodate for #frames.
+ validatePlaybackHeadPosition(track, expectedFrames,
+ "Changing start threshold to buffer data level should start streaming");
+ }
+
+ // Start threshold levels that we check.
+ private enum ThresholdLevel { LOW, MEDIUM, HIGH };
+ @Test
+ public void testStartThresholdInFrames() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+
+ for (ThresholdLevel level : new ThresholdLevel[] {
+ ThresholdLevel.LOW, ThresholdLevel.MEDIUM, ThresholdLevel.HIGH}) {
+ AudioTrack audioTrack = null;
+ try {
+ // Build our audiotrack
+ audioTrack = new AudioTrack.Builder()
+ .setAudioFormat(new AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+ .build())
+ .build();
+
+ // Initially the start threshold must be the same as the buffer size in frames.
+ final int bufferSizeInFrames = audioTrack.getBufferSizeInFrames();
+ assertEquals("At start, getBufferSizeInFrames should equal getStartThresholdInFrames",
+ bufferSizeInFrames,
+ audioTrack.getStartThresholdInFrames());
+
+ final int TARGET_THRESHOLD_IN_FRAMES; // threshold level to verify
+ switch (level) {
+ default:
+ case LOW:
+ TARGET_THRESHOLD_IN_FRAMES = 2;
+ break;
+ case MEDIUM:
+ TARGET_THRESHOLD_IN_FRAMES = bufferSizeInFrames / 2;
+ break;
+ case HIGH:
+ TARGET_THRESHOLD_IN_FRAMES = bufferSizeInFrames - 1;
+ break;
+ }
+
+ // Skip extreme cases that don't need testing.
+ if (TARGET_THRESHOLD_IN_FRAMES < 2
+ || TARGET_THRESHOLD_IN_FRAMES >= bufferSizeInFrames) continue;
+
+ // Start the AudioTrack. Now the track is waiting for data.
+ audioTrack.play();
+
+ validateWriteStartsStream(audioTrack, TARGET_THRESHOLD_IN_FRAMES);
+
+ // Try a condition that requires buffers to be filled again.
+ if (false) {
+ // Only a deep underrun when the track becomes inactive requires a refill.
+ // Disabled as this is dependent on underlying MixerThread timeouts.
+ Thread.sleep(5000 /* millis */);
+ } else {
+ // Flushing will require a refill (this does not require timing).
+ audioTrack.pause();
+ audioTrack.flush();
+ audioTrack.play();
+ }
+
+ // Check that reducing to a smaller threshold will start the track streaming.
+ validateSetStartThresholdStartsStream(audioTrack, TARGET_THRESHOLD_IN_FRAMES - 1);
+ } finally {
+ if (audioTrack != null) {
+ audioTrack.release();
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testStartThresholdInFramesExceptions() throws Exception {
+ if (!hasAudioOutput()) {
+ return;
+ }
+ AudioTrack audioTrack = null;
+ try {
+ // Build our audiotrack
+ audioTrack = new AudioTrack.Builder()
+ .setAudioFormat(new AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+ .build())
+ .build();
+
+ // Test setting invalid start threshold.
+ final AudioTrack track = audioTrack; // make final for lambda
+ assertThrows(IllegalArgumentException.class, () -> {
+ track.setStartThresholdInFrames(-1 /* startThresholdInFrames */);
+ });
+ } finally {
+ if (audioTrack != null) {
+ audioTrack.release();
+ }
+ }
+ // If we're here audioTrack should be non-null but released,
+ // so calls should return an IllegalStateException.
+ final AudioTrack track = audioTrack; // make final for lambda
+ assertThrows(IllegalStateException.class, () -> {
+ track.getStartThresholdInFrames();
+ });
+ assertThrows(IllegalStateException.class, () -> {
+ track.setStartThresholdInFrames(1 /* setStartThresholdInFrames */);
+ });
+ }
+
/* 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/CodecState.java b/tests/tests/media/src/android/media/cts/CodecState.java
index a93a1f5..cfee146 100644
--- a/tests/tests/media/src/android/media/cts/CodecState.java
+++ b/tests/tests/media/src/android/media/cts/CodecState.java
@@ -19,6 +19,7 @@
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
@@ -420,4 +421,10 @@
}
mCodec.setOutputSurface(surface);
}
+
+ public void setVideoPeek(boolean enable) {
+ Bundle parameters = new Bundle();
+ parameters.putInt(MediaCodec.PARAMETER_KEY_TUNNEL_PEEK, enable ? 1 : 0);
+ mCodec.setParameters(parameters);
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index ffc69e9..2ee0a9c 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -95,8 +95,6 @@
private static final int SLEEP_TIME_MS = 1000;
private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
- private static final String AUDIO_URL_KEY = "decoder_test_audio_url";
- private static final String VIDEO_URL_KEY = "decoder_test_video_url";
private static final String MODULE_NAME = "CtsMediaTestCases";
private DynamicConfigDeviceSide dynamicConfig;
private DisplayManager mDisplayManager;
@@ -3595,14 +3593,17 @@
return (codecName == null) ? false : true;
}
-
/**
* Test tunneled video playback mode if supported
+ *
+ * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
*/
- public void testTunneledVideoPlayback() throws Exception {
- if (!isVideoFeatureSupported(MediaFormat.MIMETYPE_VIDEO_AVC,
+ private void tunneledVideoPlayback(String mimeType, String videoName) throws Exception {
+ if (!isVideoFeatureSupported(mimeType,
CodecCapabilities.FEATURE_TunneledPlayback)) {
- MediaUtils.skipTest(TAG, "No tunneled video playback codec found!");
+ MediaUtils.skipTest(
+ TAG,
+ "No tunneled video playback codec found for MIME " + mimeType);
return;
}
@@ -3610,10 +3611,9 @@
mMediaCodecPlayer = new MediaCodecTunneledPlayer(
getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
- Uri audioUri = Uri.parse(dynamicConfig.getValue(AUDIO_URL_KEY));
- Uri videoUri = Uri.parse(dynamicConfig.getValue(VIDEO_URL_KEY));
- mMediaCodecPlayer.setAudioDataSource(audioUri, null);
- mMediaCodecPlayer.setVideoDataSource(videoUri, null);
+ Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
+ mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+ mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
@@ -3639,12 +3639,40 @@
}
/**
- * Test tunneled video playback flush if supported
+ * Test tunneled video playback mode with HEVC if supported
*/
- public void testTunneledVideoFlush() throws Exception {
- if (!isVideoFeatureSupported(MediaFormat.MIMETYPE_VIDEO_AVC,
- CodecCapabilities.FEATURE_TunneledPlayback)) {
- MediaUtils.skipTest(TAG, "No tunneled video playback codec found!");
+ public void testTunneledVideoPlaybackHevc() throws Exception {
+ tunneledVideoPlayback(MediaFormat.MIMETYPE_VIDEO_HEVC,
+ "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+ }
+
+ /**
+ * Test tunneled video playback mode with AVC if supported
+ */
+ public void testTunneledVideoPlaybackAvc() throws Exception {
+ tunneledVideoPlayback(MediaFormat.MIMETYPE_VIDEO_AVC,
+ "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+ }
+
+ /**
+ * Test tunneled video playback mode with VP9 if supported
+ */
+ public void testTunneledVideoPlaybackVp9() throws Exception {
+ tunneledVideoPlayback(MediaFormat.MIMETYPE_VIDEO_VP9,
+ "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+ }
+
+ /**
+ * Test tunneled video playback flush if supported
+ *
+ * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
+ */
+ private void testTunneledVideoFlush(String mimeType, String videoName) throws Exception {
+ if (!isVideoFeatureSupported(mimeType,
+ CodecCapabilities.FEATURE_TunneledPlayback)) {
+ MediaUtils.skipTest(
+ TAG,
+ "No tunneled video playback codec found for MIME " + mimeType);
return;
}
@@ -3652,10 +3680,9 @@
mMediaCodecPlayer = new MediaCodecTunneledPlayer(
getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
- Uri audioUri = Uri.parse(dynamicConfig.getValue(AUDIO_URL_KEY));
- Uri videoUri = Uri.parse(dynamicConfig.getValue(VIDEO_URL_KEY));
- mMediaCodecPlayer.setAudioDataSource(audioUri, null);
- mMediaCodecPlayer.setVideoDataSource(videoUri, null);
+ Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
+ mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+ mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
@@ -3668,6 +3695,100 @@
}
/**
+ * Test tunneled video playback flush with HEVC if supported
+ */
+ public void testTunneledVideoFlushHevc() throws Exception {
+ testTunneledVideoFlush(MediaFormat.MIMETYPE_VIDEO_HEVC,
+ "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+ }
+
+ /**
+ * Test tunneled video playback flush with AVC if supported
+ */
+ public void testTunneledVideoFlushAvc() throws Exception {
+ testTunneledVideoFlush(MediaFormat.MIMETYPE_VIDEO_AVC,
+ "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+ }
+
+ /**
+ * Test tunneled video playback flush with VP9 if supported
+ */
+ public void testTunneledVideoFlushVp9() throws Exception {
+ testTunneledVideoFlush(MediaFormat.MIMETYPE_VIDEO_VP9,
+ "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+ }
+
+ /**
+ * Test tunneled video peek if supported
+ *
+ * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
+ */
+ private void testTunneledVideoPeek(String mimeType, String videoName) throws Exception {
+ if (!isVideoFeatureSupported(mimeType,
+ CodecCapabilities.FEATURE_TunneledPlayback)) {
+ MediaUtils.skipTest(
+ TAG,
+ "No tunneled video playback codec found for MIME " + mimeType);
+ return;
+ }
+
+ AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
+ mMediaCodecPlayer = new MediaCodecTunneledPlayer(
+ getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
+
+ Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
+ mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+ mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
+ mMediaCodecPlayer.setVideoPeek(true);
+ assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
+ assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
+
+ // starts video playback
+ mMediaCodecPlayer.startThread();
+
+ final long durationMs = mMediaCodecPlayer.getDuration();
+ final long timeOutMs = System.currentTimeMillis() + durationMs + 5 * 1000; // add 5 sec
+ while (!mMediaCodecPlayer.isEnded()) {
+ // Log.d(TAG, "currentPosition: " + mMediaCodecPlayer.getCurrentPosition()
+ // + " duration: " + mMediaCodecPlayer.getDuration());
+ assertTrue("Tunneled video playback timeout exceeded",
+ timeOutMs > System.currentTimeMillis());
+ Thread.sleep(SLEEP_TIME_MS);
+ if (mMediaCodecPlayer.getCurrentPosition() >= mMediaCodecPlayer.getDuration()) {
+ Log.d(TAG, "testTunneledVideoPlayback -- current pos = " +
+ mMediaCodecPlayer.getCurrentPosition() +
+ ">= duration = " + mMediaCodecPlayer.getDuration());
+ break;
+ }
+ }
+ // mMediaCodecPlayer.reset() handled in TearDown();
+ }
+
+ /**
+ * Test tunneled video peek with HEVC if supported
+ */
+ public void testTunneledVideoPeekHevc() throws Exception {
+ testTunneledVideoPeek(MediaFormat.MIMETYPE_VIDEO_HEVC,
+ "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+ }
+
+ /**
+ * Test tunneled video peek with AVC if supported
+ */
+ public void testTunneledVideoPeekAvc() throws Exception {
+ testTunneledVideoPeek(MediaFormat.MIMETYPE_VIDEO_AVC,
+ "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+ }
+
+ /**
+ * Test tunneled video peek with VP9 if supported
+ */
+ public void testTunneledVideoPeekVp9() throws Exception {
+ testTunneledVideoPeek(MediaFormat.MIMETYPE_VIDEO_VP9,
+ "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+ }
+
+ /**
* Returns list of CodecCapabilities advertising support for the given MIME type.
*/
private static List<CodecCapabilities> getCodecCapabilitiesForMimeType(String mimeType) {
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java b/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java
index 411cd14..35dd818 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java
@@ -503,4 +503,13 @@
return (int)((positionUs + 500) / 1000);
}
+ public void setVideoPeek(boolean enable) {
+ if (mVideoCodecStates == null || !(mState == STATE_IDLE || mState == STATE_PAUSED)) {
+ return;
+ }
+
+ for (CodecState state: mVideoCodecStates.values()) {
+ state.setVideoPeek(enable);
+ }
+ }
}
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
index e71132a..497b999 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
@@ -107,6 +107,7 @@
private Looper mLooper;
private MediaDrm mDrm = null;
private final Object mLock = new Object();
+ private boolean mEventListenerCalled;
private boolean mExpirationUpdateReceived;
private boolean mLostStateReceived;
@@ -283,9 +284,21 @@
synchronized(mLock) {
mDrm.setOnEventListener(new MediaDrm.OnEventListener() {
@Override
- public void onEvent(MediaDrm md, byte[] sessionId, int event,
+ public void onEvent(MediaDrm md, byte[] sid, int event,
int extra, byte[] data) {
- if (event == MediaDrm.EVENT_KEY_REQUIRED) {
+ if (md != mDrm) {
+ Log.e(TAG, "onEvent callback: drm object mismatch");
+ return;
+ } else if (!Arrays.equals(mSessionId, sid)) {
+ Log.e(TAG, "onEvent callback: sessionId mismatch: |" +
+ Arrays.toString(mSessionId) + "| vs |" + Arrays.toString(sid) + "|");
+ return;
+ }
+
+ mEventListenerCalled = true;
+ if (event == MediaDrm.EVENT_PROVISION_REQUIRED) {
+ Log.i(TAG, "MediaDrm event: Provision required");
+ } else if (event == MediaDrm.EVENT_KEY_REQUIRED) {
Log.i(TAG, "MediaDrm event: Key required");
getKeys(mDrm, initDataType, mSessionId, mDrmInitData,
keyType, clearKeyIds);
@@ -293,8 +306,12 @@
Log.i(TAG, "MediaDrm event: Key expired");
getKeys(mDrm, initDataType, mSessionId, mDrmInitData,
keyType, clearKeyIds);
+ } else if (event == MediaDrm.EVENT_VENDOR_DEFINED) {
+ Log.i(TAG, "MediaDrm event: Vendor defined");
+ } else if (event == MediaDrm.EVENT_SESSION_RECLAIMED) {
+ Log.i(TAG, "MediaDrm event: Session reclaimed");
} else {
- Log.e(TAG, "Events not supported " + event);
+ Log.e(TAG, "MediaDrm event not supported: " + event);
}
}
});
@@ -415,7 +432,8 @@
if (!preparePlayback(videoMime, videoFeatures, audioUrl, audioEncrypted, videoUrl,
videoEncrypted, videoWidth, videoHeight, scrambled, mSessionId, getSurfaces())) {
- throw new Error("Fail to set up test");
+ // TODO(b/182626189) investigate why cuttlefish does not support the requested media codec
+ return;
}
if (hasDrm) {
@@ -1237,7 +1255,7 @@
mSessionId, getSurfaces())) {
closeSession(drm, mSessionId);
stopDrm(drm);
- throw new Error("Failed test setup");
+ return;
}
} catch (Exception e) {
throw new Error("Unexpected exception ", e);
@@ -1302,7 +1320,7 @@
mSessionId, getSurfaces())) {
closeSession(drm, mSessionId);
stopDrm(drm);
- throw new Error("Failed test setup");
+ return;
}
} catch (Exception e) {
throw new Error("Unexpected exception ", e);
@@ -1336,6 +1354,106 @@
}
/**
+ * Test that after onClearEventListener is called,
+ * MediaDrm's event listener is not called.
+ *
+ * Clearkey plugin's provideKeyResponse method sends a
+ * vendor defined event to the media drm event listener
+ * for testing purpose. Check that after onClearEventListener
+ * is called, the event listener is not called.
+ */
+ @Presubmit
+ public void testClearOnEventListener() {
+
+ if (watchHasNoClearkeySupport()) {
+ return;
+ }
+
+ MediaDrm drm = null;
+ mSessionId = null;
+ mEventListenerCalled = false;
+
+ // provideKeyResponse in clearkey plugin sends a
+ // vendor defined event to test the event listener;
+ // we therefore start a license request which will
+ // call provideKeyResonpse
+ byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
+ int keyType = MediaDrm.KEY_TYPE_STREAMING;
+ String initDataType = new String("cenc");
+
+ drm = startDrm(clearKeyIds, initDataType, CLEARKEY_SCHEME_UUID, keyType);
+ mSessionId = openSession(drm);
+ try {
+ if (!preparePlayback(
+ MIME_VIDEO_AVC,
+ new String[] { CodecCapabilities.FEATURE_SecurePlayback },
+ Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false /* audioEncrypted */ ,
+ Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
+ VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
+ mSessionId, getSurfaces())) {
+ closeSession(drm, mSessionId);
+ stopDrm(drm);
+ return;
+ }
+ } catch (Exception e) {
+ throw new Error("Unexpected exception ", e);
+ }
+
+ // test that the onEvent listener is called
+ mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+ getKeys(drm, initDataType, mSessionId, mDrmInitData,
+ keyType, clearKeyIds);
+
+ // wait for the vendor defined event, it should not arrive
+ // because the event listener is cleared
+ try {
+ // wait up to 2 seconds for event
+ for (int i = 0; i < 20 && !mEventListenerCalled; i++) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+ }
+ if (!mEventListenerCalled) {
+ closeSession(drm, mSessionId);
+ stopDrm(drm);
+ throw new Error("onEventListener should be called");
+ }
+ } catch (MediaDrmStateException e) {
+ closeSession(drm, mSessionId);
+ stopDrm(drm);
+ throw new Error("Unexpected exception from closing session: ", e);
+ }
+
+ // clear the drm event listener
+ // and test that the onEvent listener is not called
+ mEventListenerCalled = false;
+ drm.clearOnEventListener();
+ getKeys(drm, initDataType, mSessionId, mDrmInitData,
+ keyType, clearKeyIds);
+
+ // wait for the vendor defined event, it should not arrive
+ // because the event listener is cleared
+ try {
+ closeSession(drm, mSessionId);
+ // wait up to 2 seconds for event
+ for (int i = 0; i < 20 && !mEventListenerCalled; i++) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+ }
+ if (mEventListenerCalled) {
+ throw new Error("onEventListener should not be called");
+ }
+ } catch (MediaDrmStateException e) {
+ throw new Error("Unexpected exception from closing session: ", e);
+ } finally {
+ stopDrm(drm);
+ }
+ }
+
+ /**
* Test that the framework handles a device returning invoking
* the ::android::hardware::drm@1.2::sendSessionLostState callback
* Expected behavior: OnSessionLostState is called with
diff --git a/tests/tests/media/src/android/media/cts/MediaFormatTest.java b/tests/tests/media/src/android/media/cts/MediaFormatTest.java
index 2beae2b..fb27c56 100644
--- a/tests/tests/media/src/android/media/cts/MediaFormatTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaFormatTest.java
@@ -589,12 +589,16 @@
public void testMediaFormatConstructors() {
MediaFormat format;
{
- format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 6);
- assertEquals(MediaFormat.MIMETYPE_AUDIO_AAC, format.getString(MediaFormat.KEY_MIME));
- assertEquals(48000, format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
- assertEquals(6, format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
- assertEquals(3, format.getKeys().size());
- assertEquals(0, format.getFeatures().size());
+ String[] audioMimeTypes = { MediaFormat.MIMETYPE_AUDIO_AAC,
+ MediaFormat.MIMETYPE_AUDIO_MPEGH_MHA1, MediaFormat.MIMETYPE_AUDIO_MPEGH_MHM1 };
+ for (String mime : audioMimeTypes) {
+ format = MediaFormat.createAudioFormat(mime, 48000, 6);
+ assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
+ assertEquals(48000, format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
+ assertEquals(6, format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+ assertEquals(3, format.getKeys().size());
+ assertEquals(0, format.getFeatures().size());
+ }
}
{
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
index c545beb..9f46d39 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
@@ -15,6 +15,8 @@
*/
package android.media.cts;
+import static android.Manifest.permission.MEDIA_CONTENT_CONTROL;
+
import android.platform.test.annotations.AppModeFull;
import com.android.compatibility.common.util.SystemUtil;
@@ -36,7 +38,6 @@
import android.os.Process;
import android.test.InstrumentationTestCase;
import android.test.UiThreadTest;
-import android.util.Log;
import android.view.KeyEvent;
import java.io.IOException;
@@ -44,6 +45,7 @@
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@AppModeFull(reason = "TODO: evaluate and port to instant")
@@ -63,6 +65,7 @@
@Override
protected void tearDown() throws Exception {
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
super.tearDown();
}
@@ -76,6 +79,15 @@
// TODO enable a notification listener, test again, disable, test again
}
+ public void testGetMediaKeyEventSession() throws Exception {
+ try {
+ mSessionManager.getMediaKeyEventSession();
+ fail("Expected security exception for call to getMediaKeyEventSession");
+ } catch (SecurityException ex) {
+ // Expected
+ }
+ }
+
@UiThreadTest
public void testAddOnActiveSessionsListener() throws Exception {
try {
@@ -528,6 +540,23 @@
}
}
+ private class MediaKeyEventSessionListener
+ implements MediaSessionManager.OnMediaKeyEventSessionChangedListener {
+ final CountDownLatch mCountDownLatch;
+ MediaSession.Token mSessionToken;
+
+ MediaKeyEventSessionListener() {
+ mCountDownLatch = new CountDownLatch(1);
+ }
+
+ @Override
+ public void onMediaKeyEventSessionChanged(String packageName,
+ MediaSession.Token sessionToken) {
+ mCountDownLatch.countDown();
+ mSessionToken = sessionToken;
+ }
+ }
+
private static class HandlerExecutor implements Executor {
private final Handler mHandler;
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolTest.java b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
index e4df4ac..c345816 100644
--- a/tests/tests/media/src/android/media/cts/SoundPoolTest.java
+++ b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
@@ -16,26 +16,32 @@
package android.media.cts;
-import android.media.cts.R;
-
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.SoundPool;
+import android.media.cts.R;
import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-
+import androidx.test.InstrumentationRegistry;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicInteger;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
@AppModeFull(reason = "TODO: evaluate and port to instant")
-abstract class SoundPoolTest extends AndroidTestCase {
+@RunWith(JUnitParamsRunner.class)
+abstract class SoundPoolTest {
private static final int SOUNDPOOL_STREAMS = 4;
private static final int PRIORITY = 1;
@@ -71,20 +77,22 @@
return sounds;
}
+ private static Context getContext() {
+ return InstrumentationRegistry.getInstrumentation().getTargetContext();
+ }
+
protected AudioAttributes getAudioAttributes() {
return new AudioAttributes.Builder()
.setLegacyStreamType(AudioManager.STREAM_MUSIC).build();
}
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mFile = new File(mContext.getFilesDir(), getFileName());
+ @Before
+ public void setUp() throws Exception {
+ mFile = new File(getContext().getFilesDir(), getFileName());
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
+ @After
+ public void tearDown() throws Exception {
if (mFile.exists()) {
mFile.delete();
}
@@ -95,15 +103,16 @@
}
}
+ @Test
public void testLoad() throws Exception {
mSoundPool = new SoundPool.Builder().setMaxStreams(SOUNDPOOL_STREAMS)
.setAudioAttributes(getAudioAttributes()).build();
- int sampleId1 = mSoundPool.load(mContext, getSoundA(), PRIORITY);
+ int sampleId1 = mSoundPool.load(getContext(), getSoundA(), PRIORITY);
waitUntilLoaded(sampleId1);
// should return true, but returns false
mSoundPool.unload(sampleId1);
- AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(getSoundCs());
+ AssetFileDescriptor afd = getContext().getResources().openRawResourceFd(getSoundCs());
int sampleId2;
sampleId2 = mSoundPool.load(afd, PRIORITY);
waitUntilLoaded(sampleId2);
@@ -129,7 +138,7 @@
FileOutputStream fOutput = null;
try {
fOutput = new FileOutputStream(f);
- InputStream is = mContext.getResources().openRawResource(getSoundA());
+ InputStream is = getContext().getResources().openRawResource(getSoundA());
byte[] buffer = new byte[1024];
int length = is.read(buffer);
while (length != -1) {
@@ -144,8 +153,14 @@
}
}
- public void testSoundPoolOp() throws Exception {
- mSoundPool = new SoundPool.Builder().setMaxStreams(SOUNDPOOL_STREAMS)
+ /**
+ * Parameterized tests consider 1, 2, 4 streams in the SoundPool.
+ */
+
+ @Test
+ @Parameters({"1", "2", "4"})
+ public void testSoundPoolOp(int streamCount) throws Exception {
+ mSoundPool = new SoundPool.Builder().setMaxStreams(streamCount)
.setAudioAttributes(getAudioAttributes()).build();
int sampleID = loadSampleSync(getSoundA(), PRIORITY);
@@ -187,8 +202,10 @@
mSoundPool.unload(sampleID);
}
- public void testMultiSound() throws Exception {
- mSoundPool = new SoundPool.Builder().setMaxStreams(SOUNDPOOL_STREAMS)
+ @Test
+ @Parameters({"1", "2", "4"})
+ public void testMultiSound(int streamCount) throws Exception {
+ mSoundPool = new SoundPool.Builder().setMaxStreams(streamCount)
.setAudioAttributes(getAudioAttributes()).build();
int sampleID1 = loadSampleSync(getSoundA(), PRIORITY);
int sampleID2 = loadSampleSync(getSoundCs(), PRIORITY);
@@ -217,8 +234,10 @@
mSoundPool = null;
}
- public void testLoadMore() throws Exception {
- mSoundPool = new SoundPool.Builder().setMaxStreams(SOUNDPOOL_STREAMS)
+ @Test
+ @Parameters({"1", "2", "4"})
+ public void testLoadMore(int streamCount) throws Exception {
+ mSoundPool = new SoundPool.Builder().setMaxStreams(streamCount)
.setAudioAttributes(getAudioAttributes()).build();
int[] sounds = getSounds();
int[] soundIds = new int[sounds.length];
@@ -241,6 +260,7 @@
mSoundPool.release();
}
+ @Test
public void testAutoPauseResume() throws Exception {
// The number of possible SoundPool streams simultaneously active is limited by
// track resources. Generally this is no greater than 32, but the actual
@@ -283,7 +303,7 @@
// initiate loading
final int[] soundIds = new int[TEST_STREAMS];
for (int i = 0; i < soundIds.length; i++) {
- soundIds[i] = soundPool.load(mContext, sounds[i % sounds.length], PRIORITY);
+ soundIds[i] = soundPool.load(getContext(), sounds[i % sounds.length], PRIORITY);
}
// wait for all sounds to load,
@@ -346,7 +366,7 @@
* @throws InterruptedException
*/
private int loadSampleSync(int sampleId, int prio) throws InterruptedException {
- int sample = mSoundPool.load(mContext, sampleId, prio);
+ int sample = mSoundPool.load(getContext(), sampleId, prio);
waitUntilLoaded(sample);
return sample;
}
diff --git a/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java b/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
index 62d4112..b55f600 100644
--- a/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
+++ b/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
@@ -16,26 +16,32 @@
package android.media.cts;
+import static android.content.Context.AUDIO_SERVICE;
import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
+import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
import static android.media.cts.StubMediaRoute2ProviderService.FEATURE_SAMPLE;
import static android.media.cts.StubMediaRoute2ProviderService.FEATURE_SPECIAL;
import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID1;
import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID2;
import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID3_SESSION_CREATION_FAILED;
+import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID4_TO_SELECT_AND_DESELECT;
+import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID5_TO_TRANSFER_TO;
import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID_VARIABLE_VOLUME;
import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_NAME2;
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;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.Context;
+import android.media.AudioManager;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
+import android.media.MediaRouter2.ControllerCallback;
import android.media.MediaRouter2.RouteCallback;
import android.media.MediaRouter2.RoutingController;
import android.media.MediaRouter2.TransferCallback;
@@ -76,6 +82,7 @@
private MediaRouter2 mAppRouter2;
private Executor mExecutor;
+ private AudioManager mAudioManager;
private StubMediaRoute2ProviderService mService;
private static final int TIMEOUT_MS = 5000;
@@ -101,6 +108,7 @@
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
mExecutor = Executors.newSingleThreadExecutor();
+ mAudioManager = (AudioManager) mContext.getSystemService(AUDIO_SERVICE);
MediaRouter2TestActivity.startActivity(mContext);
mSystemRouter2ForCts = MediaRouter2.getInstance(mContext, mContext.getPackageName());
@@ -178,6 +186,14 @@
}
@Test
+ public void testGetController() {
+ String systemControllerId = mSystemRouter2ForCts.getSystemController().getId();
+ RoutingController controllerById = mSystemRouter2ForCts.getController(systemControllerId);
+ assertNotNull(controllerById);
+ assertEquals(systemControllerId, controllerById.getId());
+ }
+
+ @Test
public void testGetAllRoutes() throws Exception {
waitAndGetRoutes(FEATURE_SPECIAL);
@@ -233,8 +249,7 @@
}
};
mRouteCallbacks.add(routeCallback);
- mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
- RouteDiscoveryPreference.EMPTY);
+ mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback);
mService.addRoute(routeToAdd);
assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -261,15 +276,14 @@
}
};
mRouteCallbacks.add(routeCallback);
- mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
- RouteDiscoveryPreference.EMPTY);
+ mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback);
mService.removeRoute(ROUTE_ID2);
assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@Test
- public void testSetRouteVolumeAndRouteCallbackOnRoutesChanged() throws Exception {
+ public void testRouteCallbackOnRoutesChanged() throws Exception {
mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
@@ -299,13 +313,59 @@
}
};
mRouteCallbacks.add(routeCallback);
- mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
- RouteDiscoveryPreference.EMPTY);
+ mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback);
mSystemRouter2ForCts.setRouteVolume(routeToChangeVolume, targetVolume);
assertTrue(changedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
+
+ @Test
+ public void testRouteCallbackOnRoutesChanged_whenLocalVolumeChanged() throws Exception {
+ if (mAudioManager.isVolumeFixed()) {
+ return;
+ }
+
+ waitAndGetRoutes(FEATURE_LIVE_AUDIO);
+
+ final int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ final int minVolume = mAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
+ final int originalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+
+ MediaRoute2Info selectedSystemRoute =
+ mSystemRouter2ForCts.getSystemController().getSelectedRoutes().get(0);
+
+ assertEquals(maxVolume, selectedSystemRoute.getVolumeMax());
+ assertEquals(originalVolume, selectedSystemRoute.getVolume());
+ assertEquals(PLAYBACK_VOLUME_VARIABLE, selectedSystemRoute.getVolumeHandling());
+
+ final int targetVolume = originalVolume == minVolume
+ ? originalVolume + 1 : originalVolume - 1;
+ final CountDownLatch latch = new CountDownLatch(1);
+ RouteCallback routeCallback = new RouteCallback() {
+ @Override
+ public void onRoutesChanged(List<MediaRoute2Info> routes) {
+ for (MediaRoute2Info route : routes) {
+ if (route.getId().equals(selectedSystemRoute.getId())
+ && route.getVolume() == targetVolume) {
+ latch.countDown();
+ break;
+ }
+ }
+ }
+ };
+
+ mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback);
+
+ try {
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, targetVolume, 0);
+ assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ mSystemRouter2ForCts.unregisterRouteCallback(routeCallback);
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
+ }
+ }
+
@Test
public void testRouteCallbackOnPreferredFeaturesChanged() throws Exception {
String testFeature = "testFeature";
@@ -322,8 +382,7 @@
}
};
mRouteCallbacks.add(routeCallback);
- mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
- RouteDiscoveryPreference.EMPTY);
+ mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback);
mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
new RouteDiscoveryPreference.Builder(testFeatures, true).build());
@@ -331,7 +390,7 @@
}
@Test
- public void testTransferToSuccess() throws Exception {
+ public void testTransferTo_succeeds_onTransferCalled() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
MediaRoute2Info route = routes.get(ROUTE_ID1);
assertNotNull(route);
@@ -363,6 +422,12 @@
mSystemRouter2ForCts.transferTo(route);
assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ List<RoutingController> controllersFromGetControllers =
+ mSystemRouter2ForCts.getControllers();
+ assertEquals(2, controllersFromGetControllers.size());
+ assertTrue(createRouteMap(controllersFromGetControllers.get(1).getSelectedRoutes())
+ .containsKey(ROUTE_ID1));
+
// onSessionCreationFailed should not be called.
assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
} finally {
@@ -372,7 +437,7 @@
}
@Test
- public void testTransferToFailure() throws Exception {
+ public void testTransferTo_fails_onTransferFailureCalled() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
assertNotNull(route);
@@ -410,7 +475,6 @@
}
}
-
@Test
public void testTransferToTwice() throws Exception {
final CountDownLatch successLatch1 = new CountDownLatch(1);
@@ -492,6 +556,414 @@
}
}
+ // Same test with testTransferTo_succeeds_onTransferCalled,
+ // but with MediaRouter2#transfer(controller, route) instead of transferTo(route).
+ @Test
+ public void testTransfer_succeeds_onTransferCalled() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+ MediaRoute2Info route = routes.get(ROUTE_ID1);
+ assertNotNull(route);
+
+ final CountDownLatch successLatch = new CountDownLatch(1);
+ final CountDownLatch failureLatch = new CountDownLatch(1);
+ final List<RoutingController> controllers = new ArrayList<>();
+
+ // Create session with this route
+ TransferCallback transferCallback = new TransferCallback() {
+ @Override
+ public void onTransfer(RoutingController oldController,
+ RoutingController newController) {
+ assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+ assertTrue(createRouteMap(newController.getSelectedRoutes())
+ .containsKey(ROUTE_ID1));
+ controllers.add(newController);
+ successLatch.countDown();
+ }
+
+ @Override
+ public void onTransferFailure(MediaRoute2Info requestedRoute) {
+ failureLatch.countDown();
+ }
+ };
+
+ try {
+ mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+ mSystemRouter2ForCts.transfer(mSystemRouter2ForCts.getSystemController(), route);
+ assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ List<RoutingController> controllersFromGetControllers =
+ mSystemRouter2ForCts.getControllers();
+ assertEquals(2, controllersFromGetControllers.size());
+ assertTrue(createRouteMap(controllersFromGetControllers.get(1).getSelectedRoutes())
+ .containsKey(ROUTE_ID1));
+
+ // onSessionCreationFailed should not be called.
+ assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ releaseControllers(controllers);
+ mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+ }
+ }
+
+ @Test
+ public void testStop() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+ MediaRoute2Info route = routes.get(ROUTE_ID1);
+ assertNotNull(route);
+
+ final CountDownLatch onTransferLatch = new CountDownLatch(1);
+ final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+ final CountDownLatch onStopLatch = new CountDownLatch(1);
+ final List<RoutingController> controllers = new ArrayList<>();
+
+ TransferCallback transferCallback = new TransferCallback() {
+ @Override
+ public void onTransfer(RoutingController oldController,
+ RoutingController newController) {
+ assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+ assertTrue(createRouteMap(newController.getSelectedRoutes())
+ .containsKey(ROUTE_ID1));
+ controllers.add(newController);
+ onTransferLatch.countDown();
+ }
+ @Override
+ public void onStop(RoutingController controller) {
+ if (onTransferLatch.getCount() != 0
+ || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+ return;
+ }
+ onStopLatch.countDown();
+ }
+ };
+
+ ControllerCallback controllerCallback = new ControllerCallback() {
+ @Override
+ public void onControllerUpdated(RoutingController controller) {
+ if (onTransferLatch.getCount() != 0
+ || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+ return;
+ }
+ onControllerUpdatedLatch.countDown();
+ }
+ };
+
+ try {
+ mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+ mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+ mSystemRouter2ForCts.transferTo(route);
+ assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ assertEquals(1, controllers.size());
+ RoutingController controller = controllers.get(0);
+
+ mSystemRouter2ForCts.stop();
+
+ // Select ROUTE_ID4_TO_SELECT_AND_DESELECT
+ MediaRoute2Info routeToSelect = routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT);
+ assertNotNull(routeToSelect);
+
+ // This call should be ignored.
+ // The onControllerUpdated() shouldn't be called.
+ controller.selectRoute(routeToSelect);
+ assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+
+ // onStop should be called.
+ assertTrue(onStopLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ releaseControllers(controllers);
+ mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+ mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+ }
+ }
+
+ @Test
+ public void testRoutingControllerSelectAndDeselectRoute() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+ MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
+ assertNotNull(routeToBegin);
+
+ final CountDownLatch onTransferLatch = new CountDownLatch(1);
+ final CountDownLatch onControllerUpdatedLatchForSelect = new CountDownLatch(1);
+ final CountDownLatch onControllerUpdatedLatchForDeselect = new CountDownLatch(1);
+ final List<RoutingController> controllers = new ArrayList<>();
+
+ // Create session with ROUTE_ID1
+ TransferCallback transferCallback = new TransferCallback() {
+ @Override
+ public void onTransfer(RoutingController oldController,
+ RoutingController newController) {
+ assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+ assertTrue(createRouteMap(newController.getSelectedRoutes())
+ .containsKey(ROUTE_ID1));
+ controllers.add(newController);
+ onTransferLatch.countDown();
+ }
+ };
+
+ ControllerCallback controllerCallback = new ControllerCallback() {
+ @Override
+ public void onControllerUpdated(RoutingController controller) {
+ if (onTransferLatch.getCount() != 0
+ || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+ return;
+ }
+
+ if (onControllerUpdatedLatchForSelect.getCount() != 0) {
+ assertEquals(2, controller.getSelectedRoutes().size());
+ assertTrue(createRouteMap(controller.getSelectedRoutes())
+ .containsKey(ROUTE_ID1));
+ assertTrue(createRouteMap(controller.getSelectedRoutes())
+ .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+ assertFalse(createRouteMap(controller.getSelectableRoutes())
+ .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+ assertTrue(createRouteMap(controller.getDeselectableRoutes())
+ .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+ controllers.add(controller);
+ onControllerUpdatedLatchForSelect.countDown();
+ } else {
+ assertEquals(1, controller.getSelectedRoutes().size());
+ assertTrue(createRouteMap(controller.getSelectedRoutes())
+ .containsKey(ROUTE_ID1));
+ assertFalse(createRouteMap(controller.getSelectedRoutes())
+ .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+ assertTrue(createRouteMap(controller.getSelectableRoutes())
+ .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+ assertFalse(createRouteMap(controller.getDeselectableRoutes())
+ .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+ onControllerUpdatedLatchForDeselect.countDown();
+ }
+ }
+ };
+
+ try {
+ mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+ mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+ mSystemRouter2ForCts.transferTo(routeToBegin);
+ assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ assertEquals(1, controllers.size());
+ RoutingController controller = controllers.get(0);
+ assertTrue(createRouteMap(controller.getSelectableRoutes())
+ .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+ // Select ROUTE_ID4_TO_SELECT_AND_DESELECT
+ MediaRoute2Info routeToSelectAndDeselect = routes.get(
+ ROUTE_ID4_TO_SELECT_AND_DESELECT);
+ assertNotNull(routeToSelectAndDeselect);
+
+ controller.selectRoute(routeToSelectAndDeselect);
+ assertTrue(onControllerUpdatedLatchForSelect.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ // Note that the updated controller is a different instance.
+ assertEquals(2, controllers.size());
+ assertEquals(controllers.get(0).getId(), controllers.get(1).getId());
+ RoutingController updatedController = controllers.get(1);
+ updatedController.deselectRoute(routeToSelectAndDeselect);
+ assertTrue(onControllerUpdatedLatchForDeselect.await(
+ TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ releaseControllers(controllers);
+ mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+ mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+ }
+ }
+
+ @Test
+ public void testRoutingControllerTransferToRoute() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+ MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
+ assertNotNull(routeToBegin);
+
+ final CountDownLatch onTransferLatch = new CountDownLatch(1);
+ final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+ final List<RoutingController> controllers = new ArrayList<>();
+
+ // Create session with ROUTE_ID1
+ TransferCallback transferCallback = new TransferCallback() {
+ @Override
+ public void onTransfer(RoutingController oldController,
+ RoutingController newController) {
+ assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+ assertTrue(createRouteMap(newController.getSelectedRoutes())
+ .containsKey(ROUTE_ID1));
+ controllers.add(newController);
+ onTransferLatch.countDown();
+ }
+ };
+
+ ControllerCallback controllerCallback = new ControllerCallback() {
+ @Override
+ public void onControllerUpdated(RoutingController controller) {
+ if (onTransferLatch.getCount() != 0
+ || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+ return;
+ }
+ assertEquals(1, controller.getSelectedRoutes().size());
+ assertFalse(createRouteMap(controller.getSelectedRoutes())
+ .containsKey(ROUTE_ID1));
+ assertTrue(createRouteMap(controller.getSelectedRoutes())
+ .containsKey(ROUTE_ID5_TO_TRANSFER_TO));
+ onControllerUpdatedLatch.countDown();
+ }
+ };
+
+ try {
+ mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+ mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+ mSystemRouter2ForCts.transferTo(routeToBegin);
+ assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ assertEquals(1, controllers.size());
+ RoutingController controller = controllers.get(0);
+
+ // Transfer to ROUTE_ID5_TO_TRANSFER_TO
+ MediaRoute2Info routeToTransferTo = routes.get(ROUTE_ID5_TO_TRANSFER_TO);
+ assertNotNull(routeToTransferTo);
+
+ mSystemRouter2ForCts.transferTo(routeToTransferTo);
+ assertTrue(onControllerUpdatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ releaseControllers(controllers);
+ mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+ mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+ }
+ }
+
+ @Test
+ public void testRoutingControllerSetSessionVolume() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+ MediaRoute2Info route = routes.get(ROUTE_ID1);
+ assertNotNull(route);
+
+ CountDownLatch successLatch = new CountDownLatch(1);
+ CountDownLatch volumeChangedLatch = new CountDownLatch(1);
+
+ List<RoutingController> controllers = new ArrayList<>();
+
+ // Create session with this route
+ TransferCallback transferCallback = new TransferCallback() {
+ @Override
+ public void onTransfer(RoutingController oldController,
+ RoutingController newController) {
+ controllers.add(newController);
+ successLatch.countDown();
+ }
+ };
+
+ try {
+ mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+ mSystemRouter2ForCts.transferTo(route);
+
+ assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+ }
+
+ assertEquals(1, controllers.size());
+
+ // test setSessionVolume
+ RoutingController targetController = controllers.get(0);
+ assertEquals(PLAYBACK_VOLUME_VARIABLE, targetController.getVolumeHandling());
+ int currentVolume = targetController.getVolume();
+ int maxVolume = targetController.getVolumeMax();
+ int targetVolume = (currentVolume == maxVolume) ? currentVolume - 1 : (currentVolume + 1);
+
+ ControllerCallback controllerCallback = new ControllerCallback() {
+ @Override
+ public void onControllerUpdated(MediaRouter2.RoutingController controller) {
+ if (!TextUtils.equals(targetController.getId(), controller.getId())) {
+ return;
+ }
+ if (controller.getVolume() == targetVolume) {
+ volumeChangedLatch.countDown();
+ }
+ }
+ };
+
+ try {
+ mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+ targetController.setVolume(targetVolume);
+ assertTrue(volumeChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ releaseControllers(controllers);
+ mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+ }
+ }
+
+ @Test
+ public void testRoutingControllerRelease() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+ MediaRoute2Info route = routes.get(ROUTE_ID1);
+ assertNotNull(route);
+
+ final CountDownLatch onTransferLatch = new CountDownLatch(1);
+ final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+ final CountDownLatch onStopLatch = new CountDownLatch(1);
+ final List<RoutingController> controllers = new ArrayList<>();
+
+ TransferCallback transferCallback = new TransferCallback() {
+ @Override
+ public void onTransfer(RoutingController oldController,
+ RoutingController newController) {
+ assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+ assertTrue(createRouteMap(newController.getSelectedRoutes())
+ .containsKey(ROUTE_ID1));
+ controllers.add(newController);
+ onTransferLatch.countDown();
+ }
+ @Override
+ public void onStop(RoutingController controller) {
+ if (onTransferLatch.getCount() != 0
+ || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+ return;
+ }
+ onStopLatch.countDown();
+ }
+ };
+
+ ControllerCallback controllerCallback = new ControllerCallback() {
+ @Override
+ public void onControllerUpdated(RoutingController controller) {
+ if (onTransferLatch.getCount() != 0
+ || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+ return;
+ }
+ onControllerUpdatedLatch.countDown();
+ }
+ };
+
+ try {
+ mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+ mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+ mSystemRouter2ForCts.transferTo(route);
+ assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ assertEquals(1, controllers.size());
+ RoutingController controller = controllers.get(0);
+
+ // Release controller. Future calls should be ignored.
+ controller.release();
+
+ // Select ROUTE_ID5_TO_TRANSFER_TO
+ MediaRoute2Info routeToSelect = routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT);
+ assertNotNull(routeToSelect);
+
+ // This call should be ignored.
+ // The onControllerUpdated() shouldn't be called.
+ controller.selectRoute(routeToSelect);
+ assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+
+ // onStop should be called.
+ assertTrue(onStopLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ releaseControllers(controllers);
+ mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+ mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+ }
+ }
+
private Map<String, MediaRoute2Info> waitAndGetRoutes(String feature) throws Exception {
List<String> features = new ArrayList<>();
features.add(feature);
@@ -512,8 +984,7 @@
}
};
- mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
- RouteDiscoveryPreference.EMPTY);
+ mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback);
try {
// Note: The routes can be added before registering the callback,
diff --git a/tests/tests/mediaparser/AndroidManifest.xml b/tests/tests/mediaparser/AndroidManifest.xml
index e3a26e0..f0f6d97 100644
--- a/tests/tests/mediaparser/AndroidManifest.xml
+++ b/tests/tests/mediaparser/AndroidManifest.xml
@@ -19,6 +19,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.media.mediaparser.cts">
+ <uses-sdk android:minSdkVersion="29"
+ android:targetSdkVersion="29"/>
<application>
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
index a62d853..8225e0c 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
@@ -16,9 +16,9 @@
package android.media.mediatranscoding.cts;
-import static org.junit.Assert.assertNotEquals;
import static org.testng.Assert.assertThrows;
+
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -28,6 +28,7 @@
import android.media.MediaTranscodeManager;
import android.media.MediaTranscodeManager.TranscodingRequest;
import android.media.MediaTranscodeManager.TranscodingSession;
+import android.media.MediaTranscodeManager.VideoTranscodingRequest;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
@@ -178,13 +179,9 @@
return;
}
assertThrows(IllegalArgumentException.class, () -> {
- TranscodingRequest request =
- new TranscodingRequest.Builder()
- .setSourceUri(mSourceHEVCVideoUri)
- .setDestinationUri(null)
- .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
- .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
- .setVideoTrackFormat(createMediaFormat())
+ VideoTranscodingRequest request =
+ new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, null,
+ createMediaFormat())
.build();
});
}
@@ -197,14 +194,10 @@
return;
}
assertThrows(IllegalArgumentException.class, () -> {
- TranscodingRequest request =
- new TranscodingRequest.Builder()
- .setSourceUri(mSourceHEVCVideoUri)
- .setDestinationUri(mDestinationUri)
- .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
- .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+ VideoTranscodingRequest request =
+ new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, mDestinationUri,
+ createMediaFormat())
.setClientPid(-1)
- .setVideoTrackFormat(createMediaFormat())
.build();
});
}
@@ -217,14 +210,10 @@
return;
}
assertThrows(IllegalArgumentException.class, () -> {
- TranscodingRequest request =
- new TranscodingRequest.Builder()
- .setSourceUri(mSourceHEVCVideoUri)
- .setDestinationUri(mDestinationUri)
- .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
- .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+ VideoTranscodingRequest request =
+ new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, mDestinationUri,
+ createMediaFormat())
.setClientUid(-1)
- .setVideoTrackFormat(createMediaFormat())
.build();
});
}
@@ -237,12 +226,8 @@
return;
}
assertThrows(IllegalArgumentException.class, () -> {
- TranscodingRequest request =
- new TranscodingRequest.Builder()
- .setSourceUri(null)
- .setDestinationUri(mDestinationUri)
- .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
- .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+ VideoTranscodingRequest request =
+ new VideoTranscodingRequest.Builder(null, mDestinationUri, createMediaFormat())
.build();
});
}
@@ -254,13 +239,9 @@
if (shouldSkip()) {
return;
}
- assertThrows(UnsupportedOperationException.class, () -> {
- TranscodingRequest request =
- new TranscodingRequest.Builder()
- .setDestinationUri(mDestinationUri)
- .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
- .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
- .setVideoTrackFormat(createMediaFormat())
+ assertThrows(IllegalArgumentException.class, () -> {
+ VideoTranscodingRequest request =
+ new VideoTranscodingRequest.Builder(null, mDestinationUri, createMediaFormat())
.build();
});
}
@@ -272,13 +253,10 @@
if (shouldSkip()) {
return;
}
- assertThrows(UnsupportedOperationException.class, () -> {
- TranscodingRequest request =
- new TranscodingRequest.Builder()
- .setSourceUri(mSourceHEVCVideoUri)
- .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
- .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
- .setVideoTrackFormat(createMediaFormat())
+ assertThrows(IllegalArgumentException.class, () -> {
+ VideoTranscodingRequest request =
+ new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, null,
+ createMediaFormat())
.build();
});
}
@@ -291,13 +269,9 @@
if (shouldSkip()) {
return;
}
- assertThrows(UnsupportedOperationException.class, () -> {
- TranscodingRequest request =
- new TranscodingRequest.Builder()
- .setSourceUri(mSourceHEVCVideoUri)
- .setDestinationUri(mDestinationUri)
- .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
- .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+ assertThrows(IllegalArgumentException.class, () -> {
+ VideoTranscodingRequest request =
+ new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, mDestinationUri, null)
.build();
});
}
@@ -309,13 +283,8 @@
}
Semaphore transcodeCompleteSemaphore = new Semaphore(0);
- TranscodingRequest request =
- new TranscodingRequest.Builder()
- .setSourceUri(srcUri)
- .setDestinationUri(dstUri)
- .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
- .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
- .setVideoTrackFormat(createMediaFormat())
+ VideoTranscodingRequest request =
+ new VideoTranscodingRequest.Builder(srcUri, dstUri, createMediaFormat())
.build();
Executor listenerExecutor = Executors.newSingleThreadExecutor();
@@ -337,13 +306,10 @@
assertTrue("Transcode failed to complete in time.", finishedOnTime);
}
- File dstFile = new File(dstUri.getPath());
+ File dstFile = new File(dstUri.getPath());;
if (expectedResult == TranscodingSession.RESULT_SUCCESS) {
// Checks the destination file get generated.
assertTrue("Failed to create destination file", dstFile.exists());
- assertEquals(TranscodingSession.ERROR_NONE, session.getErrorCode());
- } else {
- assertNotEquals(TranscodingSession.ERROR_NONE, session.getErrorCode());
}
if (dstFile.exists()) {
@@ -483,11 +449,10 @@
ApplicationMediaCapabilities clientCaps =
new ApplicationMediaCapabilities.Builder().build();
- TranscodingRequest.MediaFormatResolver
- resolver = new TranscodingRequest.MediaFormatResolver()
- .setSourceVideoFormatHint(MediaFormat.createVideoFormat(
- MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH, HEIGHT))
- .setClientCapabilities(clientCaps);
+ TranscodingRequest.VideoFormatResolver
+ resolver = new TranscodingRequest.VideoFormatResolver(clientCaps,
+ MediaFormat.createVideoFormat(
+ MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH, HEIGHT));
assertTrue(resolver.shouldTranscode());
MediaFormat videoTrackFormat = resolver.resolveVideoFormat();
assertNotNull(videoTrackFormat);
@@ -495,15 +460,10 @@
int pid = android.os.Process.myPid();
int uid = android.os.Process.myUid();
- TranscodingRequest.Builder builder =
- new TranscodingRequest.Builder()
- .setSourceUri(fileUri)
- .setDestinationUri(destinationUri)
- .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+ VideoTranscodingRequest.Builder builder =
+ new VideoTranscodingRequest.Builder(fileUri, destinationUri, videoTrackFormat)
.setClientPid(pid)
- .setClientUid(uid)
- .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
- .setVideoTrackFormat(videoTrackFormat);
+ .setClientUid(uid);
if (testFileDescriptor) {
// Open source Uri.
@@ -577,13 +537,9 @@
Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+ mContext.getCacheDir().getAbsolutePath() + "/HevcTranscode.mp4");
- TranscodingRequest request =
- new TranscodingRequest.Builder()
- .setSourceUri(mSourceHEVCVideoUri)
- .setDestinationUri(destinationUri)
- .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
- .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
- .setVideoTrackFormat(createMediaFormat())
+ VideoTranscodingRequest request =
+ new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, destinationUri,
+ createMediaFormat())
.build();
Executor listenerExecutor = Executors.newSingleThreadExecutor();
@@ -666,13 +622,9 @@
Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+ mContext.getCacheDir().getAbsolutePath() + "/HevcTranscode.mp4");
- TranscodingRequest request =
- new TranscodingRequest.Builder()
- .setSourceUri(mSourceHEVCVideoUri)
- .setDestinationUri(destinationUri)
- .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
- .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
- .setVideoTrackFormat(createMediaFormat())
+ VideoTranscodingRequest request =
+ new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, destinationUri,
+ createMediaFormat())
.build();
Executor listenerExecutor = Executors.newSingleThreadExecutor();
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
index bc9555c..17d6933 100644
--- a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
+++ b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
@@ -18,10 +18,14 @@
import static android.multiuser.cts.TestingUtils.getBooleanProperty;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import android.Manifest;
import android.app.Instrumentation;
import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
import android.os.UserManager;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -31,6 +35,11 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
@RunWith(JUnit4.class)
public final class UserManagerTest {
@@ -70,4 +79,30 @@
}
// TODO(b/173541467): add testIsUserForeground_backgroundUser()
// TODO(b/179163496): add testIsUserForeground_ tests for profile users
+
+ @Test
+ public void testCloneUser() throws Exception {
+ // Need CREATE_USERS permission to create user in test
+ mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.CREATE_USERS, Manifest.permission.INTERACT_ACROSS_USERS);
+ Set<String> disallowedPackages = new HashSet<String>();
+ UserHandle userHandle = mUserManager.createProfile(
+ "Clone user", UserManager.USER_TYPE_PROFILE_CLONE, disallowedPackages);
+ assertThat(userHandle).isNotNull();
+
+ final Context userContext = mContext.createPackageContextAsUser("system", 0,
+ userHandle);
+ assertThat(userContext.getSystemService(
+ UserManager.class).sharesMediaWithParent()).isTrue();
+
+ List<UserInfo> list = mUserManager.getUsers(true,
+ true, true);
+ List<UserInfo> cloneUsers = list.stream().filter(
+ user -> (user.id == userHandle.getIdentifier()
+ && user.isCloneProfile()))
+ .collect(Collectors.toList());
+ assertThat(cloneUsers.size()).isEqualTo(1);
+ assertThat(mUserManager.removeUser(userHandle)).isTrue();
+ mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+ }
}
diff --git a/tests/tests/nativemedia/resourceobserver/Android.bp b/tests/tests/nativemedia/resourceobserver/Android.bp
new file mode 100644
index 0000000..d4d77b0
--- /dev/null
+++ b/tests/tests/nativemedia/resourceobserver/Android.bp
@@ -0,0 +1,45 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "ResourceObserverNativeTest",
+
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+
+ srcs: ["src/ResourceObserverNativeTest.cpp"],
+
+ shared_libs: [
+ "libbinder_ndk",
+ "liblog",
+ "libmediandk",
+ ],
+
+ static_libs: [
+ "libbase_ndk",
+ "libgtest",
+ "resourceobserver_aidl_interface-V1-ndk",
+ ],
+ whole_static_libs: [
+ "libnativetesthelper_jni"
+ ],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
diff --git a/tests/tests/nativemedia/resourceobserver/AndroidTest.xml b/tests/tests/nativemedia/resourceobserver/AndroidTest.xml
new file mode 100644
index 0000000..1f1b6bd
--- /dev/null
+++ b/tests/tests/nativemedia/resourceobserver/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 ResourceObserver 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" />
+ <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="ResourceObserverNativeTest->/data/local/tmp/ResourceObserverNativeTest" />
+ <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="ResourceObserverNativeTest" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+ <!-- Controller that will skip the module if a native bridge situation is detected -->
+ <!-- For example: module wants to run arm and device is x86 -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.NativeBridgeModuleController" />
+</configuration>
diff --git a/tests/tests/nativemedia/resourceobserver/OWNERS b/tests/tests/nativemedia/resourceobserver/OWNERS
new file mode 100644
index 0000000..5679184
--- /dev/null
+++ b/tests/tests/nativemedia/resourceobserver/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 1344
+chz@google.com
+wonsik@google.com
+lajos@google.com
diff --git a/tests/tests/nativemedia/resourceobserver/src/ResourceObserverNativeTest.cpp b/tests/tests/nativemedia/resourceobserver/src/ResourceObserverNativeTest.cpp
new file mode 100644
index 0000000..1bdffad
--- /dev/null
+++ b/tests/tests/nativemedia/resourceobserver/src/ResourceObserverNativeTest.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "ResourceObserverNativeTest"
+
+#include <aidl/android/media/BnResourceObserver.h>
+#include <aidl/android/media/IResourceObserverService.h>
+#include <android-base/logging.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <gtest/gtest.h>
+
+#include "media/NdkMediaCodec.h"
+
+using namespace android;
+using Status = ::ndk::ScopedAStatus;
+using ::aidl::android::media::BnResourceObserver;
+using ::aidl::android::media::IResourceObserverService;
+using ::aidl::android::media::MediaObservableEvent;
+using ::aidl::android::media::MediaObservableFilter;
+using ::aidl::android::media::MediaObservableParcel;
+using ::aidl::android::media::MediaObservableType;
+
+static const char* MIMETYPE_AVC = "video/avc";
+
+static std::string toString(const MediaObservableParcel& observable) {
+ return "{" + ::aidl::android::media::toString(observable.type) + ", " +
+ std::to_string(observable.value) + "}";
+}
+
+class ResourceObserverNativeTest : public ::testing::Test {
+public:
+ void SetUp() override { ABinderProcess_startThreadPool(); }
+ void TearDown() override {}
+
+ struct StatusChangeEvent {
+ MediaObservableEvent event;
+ int32_t uid;
+ int32_t pid;
+ std::vector<MediaObservableParcel> observables;
+ };
+ struct ResourceObserver : public BnResourceObserver {
+ explicit ResourceObserver() {}
+
+ // IResourceObserver
+ ::ndk::ScopedAStatus onStatusChanged(
+ MediaObservableEvent event, int32_t uid, int32_t pid,
+ const std::vector<MediaObservableParcel>& observables) override {
+ LOG(INFO) << ::aidl::android::media::toString(event) << ", uid: " << uid
+ << ", pid: " << pid << ", " << toString(observables[0]).c_str();
+
+ std::scoped_lock lock{mLock};
+ mLastEvent.event = event;
+ mLastEvent.uid = uid;
+ mLastEvent.pid = pid;
+ mLastEvent.observables = observables;
+ mStatusChangeCalled = true;
+ mCondition.notify_one();
+ return ::ndk::ScopedAStatus::ok();
+ }
+ // ~IResourceObserver
+
+ bool waitForEvent(StatusChangeEvent& event, int64_t timeoutUs = 0) {
+ std::unique_lock lock{mLock};
+ if (!mStatusChangeCalled && timeoutUs > 0) {
+ mCondition.wait_for(lock, std::chrono::microseconds(timeoutUs));
+ }
+ if (!mStatusChangeCalled) {
+ return false;
+ }
+ event = mLastEvent;
+ mStatusChangeCalled = false;
+ return true;
+ }
+ std::mutex mLock;
+ std::condition_variable mCondition;
+ bool mStatusChangeCalled = false;
+ StatusChangeEvent mLastEvent;
+ };
+
+ void testResourceObserver(MediaObservableEvent eventType) {
+ ::ndk::SpAIBinder binder(AServiceManager_getService("media.resource_observer"));
+ std::shared_ptr<IResourceObserverService> service =
+ IResourceObserverService::fromBinder(binder);
+
+ EXPECT_NE(service, nullptr);
+
+ std::shared_ptr<ResourceObserver> observer = ::ndk::SharedRefBase::make<ResourceObserver>();
+ std::vector<MediaObservableFilter> filters = {{MediaObservableType::kVideoSecureCodec,
+ eventType},
+ {MediaObservableType::kVideoNonSecureCodec,
+ eventType}};
+
+ Status status = service->registerObserver(observer, filters);
+ EXPECT_TRUE(status.isOk());
+
+ AMediaCodec* dec = AMediaCodec_createDecoderByType(MIMETYPE_AVC);
+
+ // We only test this if the AVC non-secure decoder can be created.
+ if (dec != nullptr) {
+ StatusChangeEvent event;
+ if ((uint64_t)eventType & (uint64_t)MediaObservableEvent::kBusy) {
+ EXPECT_TRUE(observer->waitForEvent(event, 100000));
+ verifyEvent(event, MediaObservableEvent::kBusy,
+ MediaObservableType::kVideoNonSecureCodec);
+ } else {
+ // Should not receive any event, wait 1 second to confirm.
+ EXPECT_FALSE(observer->waitForEvent(event, 1000000));
+ }
+
+ AMediaCodec_delete(dec);
+
+ if ((uint64_t)eventType & (uint64_t)MediaObservableEvent::kIdle) {
+ EXPECT_TRUE(observer->waitForEvent(event, 100000));
+ verifyEvent(event, MediaObservableEvent::kIdle,
+ MediaObservableType::kVideoNonSecureCodec);
+ } else {
+ // Should not receive any event, wait 1 second to confirm.
+ EXPECT_FALSE(observer->waitForEvent(event, 1000000));
+ }
+ }
+
+ status = service->unregisterObserver(observer);
+ EXPECT_TRUE(status.isOk());
+ }
+
+ void verifyEvent(const StatusChangeEvent& event, MediaObservableEvent expectedEventType,
+ MediaObservableType expectedObservableType) {
+ EXPECT_EQ(event.event, expectedEventType);
+ EXPECT_EQ(event.pid, getpid());
+ EXPECT_EQ(event.uid, getuid());
+ EXPECT_EQ(event.observables.size(), 1);
+ EXPECT_EQ(event.observables[0].type, expectedObservableType);
+ EXPECT_EQ(event.observables[0].value, 1);
+ }
+};
+
+//-------------------------------------------------------------------------------------------------
+TEST_F(ResourceObserverNativeTest, testInvalidParameters) {
+ LOG(INFO) << "testInvalidParameters";
+
+ ::ndk::SpAIBinder binder(AServiceManager_getService("media.resource_observer"));
+ std::shared_ptr<IResourceObserverService> service =
+ IResourceObserverService::fromBinder(binder);
+
+ EXPECT_NE(service, nullptr);
+
+ std::shared_ptr<ResourceObserver> observer = ::ndk::SharedRefBase::make<ResourceObserver>();
+ std::vector<MediaObservableFilter> filters = {{MediaObservableType::kVideoSecureCodec,
+ MediaObservableEvent::kAll},
+ {MediaObservableType::kVideoNonSecureCodec,
+ MediaObservableEvent::kAll}};
+ std::vector<MediaObservableFilter> emptyFilters;
+
+ // Test register with null observer fails.
+ Status status = service->registerObserver(nullptr, filters);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getServiceSpecificError(), BAD_VALUE);
+
+ // Test register with empty filter list fails.
+ status = service->registerObserver(observer, emptyFilters);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getServiceSpecificError(), BAD_VALUE);
+
+ // Test register duplicate observer fails.
+ status = service->registerObserver(observer, filters);
+ EXPECT_TRUE(status.isOk());
+ status = service->registerObserver(observer, filters);
+ EXPECT_FALSE(status.isOk());
+ EXPECT_EQ(status.getServiceSpecificError(), ALREADY_EXISTS);
+
+ status = service->unregisterObserver(observer);
+ EXPECT_TRUE(status.isOk());
+}
+
+TEST_F(ResourceObserverNativeTest, testResourceObserverBusy) {
+ LOG(INFO) << "testResourceObserverBusy";
+
+ testResourceObserver(MediaObservableEvent::kBusy);
+}
+
+TEST_F(ResourceObserverNativeTest, testResourceObserverIdle) {
+ LOG(INFO) << "testResourceObserverIdle";
+
+ testResourceObserver(MediaObservableEvent::kIdle);
+}
+
+TEST_F(ResourceObserverNativeTest, testResourceObserverAll) {
+ LOG(INFO) << "testResourceObserverAll";
+
+ testResourceObserver(MediaObservableEvent::kAll);
+}
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/Android.bp b/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
index 9f48a60..144ceac 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
@@ -36,5 +36,5 @@
"cts",
"general-tests",
],
- min_sdk_version: "27",
+ target_sdk_version: "27"
}
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
index df0d592..dd7bf07 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
@@ -38,6 +38,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.provider.Telephony.Threads;
import android.service.notification.NotificationListenerService;
@@ -303,6 +304,26 @@
InstrumentationRegistry.getInstrumentation(), false);
}
+ @Test
+ public void testChannelDeletion_cancelReason() throws Exception {
+ assertEquals(Build.VERSION_CODES.O_MR1, mContext.getApplicationInfo().targetSdkVersion);
+ toggleListenerAccess(TestNotificationListener.getId(),
+ InstrumentationRegistry.getInstrumentation(), true);
+ Thread.sleep(500); // wait for listener to be allowed
+ mListener = TestNotificationListener.getInstance();
+
+ sendNotification(566, R.drawable.icon_black);
+
+ Thread.sleep(500); // wait for notification listener to receive notification
+ assertEquals(1, mListener.mPosted.size());
+ String key = mListener.mPosted.get(0).getKey();
+
+ mNotificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+
+ assertEquals(NotificationListenerService.REASON_CHANNEL_BANNED,
+ getCancellationReason(key));
+ }
+
private void sendNotification(final int id, final int icon) throws Exception {
sendNotification(id, null, icon);
}
@@ -327,6 +348,20 @@
mNotificationManager.notify(id, notification);
}
+ private int getCancellationReason(String key) {
+ for (int tries = 3; tries-- > 0; ) {
+ if (mListener.mRemoved.containsKey(key)) {
+ return mListener.mRemoved.get(key);
+ }
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ex) {
+ // pass
+ }
+ }
+ return -1;
+ }
+
private void toggleNotificationPolicyAccess(String packageName,
Instrumentation instrumentation, boolean on) throws IOException {
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/TestNotificationListener.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/TestNotificationListener.java
index c174d81..d8542e9 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/TestNotificationListener.java
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/TestNotificationListener.java
@@ -21,6 +21,8 @@
import android.util.Log;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
public class TestNotificationListener extends NotificationListenerService {
public static final String TAG = "TestNotificationListener";
@@ -29,7 +31,7 @@
private ArrayList<String> mTestPackages = new ArrayList<>();
public ArrayList<StatusBarNotification> mPosted = new ArrayList<>();
- public ArrayList<StatusBarNotification> mRemoved = new ArrayList<>();
+ public Map<String, Integer> mRemoved = new HashMap<>();
public RankingMap mRankingMap;
private static TestNotificationListener sNotificationListenerInstance = null;
@@ -80,10 +82,11 @@
}
@Override
- public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+ int reason) {
if (!mTestPackages.contains(sbn.getPackageName())) { return; }
mRankingMap = rankingMap;
- mRemoved.add(sbn);
+ mRemoved.put(sbn.getKey(), reason);
}
@Override
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
index e46b709..af70d69 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationAssistantServiceTest.java
@@ -25,6 +25,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
import android.app.Instrumentation;
import android.app.Notification;
@@ -68,7 +69,7 @@
final String TAG = "NotAsstServiceTest";
final String NOTIFICATION_CHANNEL_ID = "NotificationAssistantServiceTest";
final int ICON_ID = android.R.drawable.sym_def_app_icon;
- final long SLEEP_TIME = 500; // milliseconds
+ final long SLEEP_TIME = 1000; // milliseconds
private TestNotificationAssistant mNotificationAssistantService;
private TestNotificationListener mNotificationListenerService;
@@ -77,6 +78,10 @@
private Context mContext;
private UiAutomation mUi;
+ private boolean isWatch() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ }
+
@Before
public void setUp() throws IOException {
mUi = InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -556,6 +561,7 @@
if (isTelevision()) {
return;
}
+ assumeFalse("Status bar service not supported", isWatch());
setUpListeners();
turnScreenOn();
mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR");
@@ -587,6 +593,7 @@
if (isTelevision()) {
return;
}
+ assumeFalse("Status bar service not supported", isWatch());
setUpListeners();
turnScreenOn();
mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR");
@@ -612,6 +619,7 @@
if (isTelevision()) {
return;
}
+ assumeFalse("Status bar service not supported", isWatch());
setUpListeners();
turnScreenOn();
mUi.adoptShellPermissionIdentity("android.permission.EXPAND_STATUS_BAR");
diff --git a/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt b/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt
index cd4f7c8..074e76d 100644
--- a/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt
+++ b/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt
@@ -16,10 +16,14 @@
package android.os.cts
+import android.app.Instrumentation
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
+import android.support.test.uiautomator.By
+import android.support.test.uiautomator.BySelector
+import android.support.test.uiautomator.UiObject2
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
@@ -43,6 +47,7 @@
const val WAIT_TIME_MS = 1000L
}
private val context: Context = InstrumentationRegistry.getTargetContext()
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private lateinit var packageManager: PackageManager
@@ -83,8 +88,16 @@
packageManager.getApplicationInfo(APK_PACKAGE_NAME_S_APP, 0 /* flags */)
val stopped = ((ai.flags and ApplicationInfo.FLAG_STOPPED) != 0)
assertTrue(stopped)
+ runShellCommandOrThrow("cmd statusbar expand-notifications")
+ waitFindObject(By.textContains("unused app"))
+ .click()
+ waitFindObject(By.text(APK_PACKAGE_NAME_S_APP))
}
}
}
}
+
+ private fun waitFindObject(selector: BySelector): UiObject2 {
+ return waitFindObject(instrumentation.uiAutomation, selector)
+ }
}
diff --git a/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt b/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
index d3b9fed..091211f 100644
--- a/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
+++ b/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
@@ -19,10 +19,13 @@
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING
import android.app.Instrumentation
+import android.app.UiAutomation
import android.content.Context
import android.os.ParcelFileDescriptor
import android.os.Process
import android.provider.DeviceConfig
+import android.support.test.uiautomator.BySelector
+import android.support.test.uiautomator.UiObject2
import androidx.test.InstrumentationRegistry
import com.android.compatibility.common.util.LogcatInspector
import com.android.compatibility.common.util.SystemUtil.eventually
@@ -30,6 +33,10 @@
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.compatibility.common.util.ThrowingSupplier
+import com.android.compatibility.common.util.UiAutomatorUtils
+import com.android.compatibility.common.util.click
+import com.android.compatibility.common.util.depthFirstSearch
+import com.android.compatibility.common.util.textAsString
import org.hamcrest.CoreMatchers
import org.hamcrest.Matcher
import org.hamcrest.Matchers
@@ -117,6 +124,30 @@
waitForIdle()
}
+fun waitFindObject(uiAutomation: UiAutomation, selector: BySelector): UiObject2 {
+ try {
+ return UiAutomatorUtils.waitFindObject(selector)
+ } catch (e: RuntimeException) {
+ val ui = uiAutomation.rootInActiveWindow
+
+ val title = ui.depthFirstSearch { node ->
+ node.viewIdResourceName?.contains("alertTitle") == true
+ }
+ val okButton = ui.depthFirstSearch { node ->
+ node.textAsString?.equals("OK", ignoreCase = true) ?: false
+ }
+
+ if (title?.text?.toString() == "Android System" && okButton != null) {
+ // Auto dismiss occasional system dialogs to prevent interfering with the test
+ android.util.Log.w(AutoRevokeTest.LOG_TAG, "Ignoring exception", e)
+ okButton.click()
+ return UiAutomatorUtils.waitFindObject(selector)
+ } else {
+ throw e
+ }
+ }
+}
+
class Logcat() : LogcatInspector() {
override fun executeShellCommand(command: String?): InputStream {
val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
diff --git a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
index c2ffcd0..faf698d 100644
--- a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
+++ b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
@@ -43,7 +43,6 @@
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.compatibility.common.util.UI_ROOT
-import com.android.compatibility.common.util.UiAutomatorUtils
import com.android.compatibility.common.util.click
import com.android.compatibility.common.util.depthFirstSearch
import com.android.compatibility.common.util.lowestCommonAncestor
@@ -385,27 +384,7 @@
}
private fun waitFindObject(selector: BySelector): UiObject2 {
- try {
- return UiAutomatorUtils.waitFindObject(selector)
- } catch (e: RuntimeException) {
- val ui = instrumentation.uiAutomation.rootInActiveWindow
-
- val title = ui.depthFirstSearch { node ->
- node.viewIdResourceName?.contains("alertTitle") == true
- }
- val okButton = ui.depthFirstSearch { node ->
- node.textAsString?.equals("OK", ignoreCase = true) ?: false
- }
-
- if (title?.text?.toString() == "Android System" && okButton != null) {
- // Auto dismiss occasional system dialogs to prevent interfering with the test
- android.util.Log.w(LOG_TAG, "Ignoring exception", e)
- okButton.click()
- return UiAutomatorUtils.waitFindObject(selector)
- } else {
- throw e
- }
- }
+ return waitFindObject(instrumentation.uiAutomation, selector)
}
}
@@ -431,13 +410,21 @@
/**
* For some reason waitFindObject sometimes fails to find UI that is present in the view hierarchy
*/
-fun waitFindNode(matcher: Matcher<AccessibilityNodeInfo>): AccessibilityNodeInfo {
+fun waitFindNode(
+ matcher: Matcher<AccessibilityNodeInfo>,
+ failMsg: String? = null
+): AccessibilityNodeInfo {
return getEventually {
val ui = UI_ROOT
ui.depthFirstSearch { node ->
matcher.matches(node)
}.assertNotNull {
- "No view found matching $matcher:\n\n${uiDump(ui)}"
+ buildString {
+ if (failMsg != null) {
+ appendLine(failMsg)
+ }
+ appendLine("No view found matching $matcher:\n\n${uiDump(ui)}")
+ }
}
}
}
diff --git a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
index 0620626..3076607 100644
--- a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
+++ b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
@@ -20,14 +20,26 @@
import android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.net.MacAddress
+import android.os.Binder
+import android.os.Bundle
+import android.os.Parcelable
import android.os.UserHandle
import android.platform.test.annotations.AppModeFull
import android.test.InstrumentationTestCase
+import android.util.Size
+import android.util.SizeF
+import android.util.SparseArray
+import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT
+import android.widget.EditText
import android.widget.TextView
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.MatcherUtils
import com.android.compatibility.common.util.MatcherUtils.hasIdThat
import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.getEventually
import com.android.compatibility.common.util.SystemUtil.runShellCommand
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
@@ -35,7 +47,10 @@
import com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject
import com.android.compatibility.common.util.children
import com.android.compatibility.common.util.click
+import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.containsString
+import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.Matcher
import org.hamcrest.Matchers.empty
import org.hamcrest.Matchers.not
import org.junit.Assert.assertThat
@@ -43,6 +58,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import java.io.Serializable
const val COMPANION_APPROVE_WIFI_CONNECTIONS =
"android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS"
@@ -140,12 +156,20 @@
installApk("/data/local/tmp/cts/os/CtsCompanionTestApp.apk")
startApp(packageName)
+ waitFindNode(hasClassThat(`is`(equalTo(EditText::class.java.name))))
+ .performAction(ACTION_SET_TEXT,
+ bundleOf(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE to ""))
+ waitForIdle()
+
click("Watch")
- click("Associate")
- val device = waitFindNode(hasIdThat(containsString("device_list")))
- .children
- .find { it.className == TextView::class.java.name }
- assumeTrue("Test requires a discoverable bluetooth device nearby", device != null)
+ val device = getEventually({
+ click("Associate")
+ waitFindNode(hasIdThat(containsString("device_list")),
+ failMsg = "Test requires a discoverable bluetooth device nearby")
+ .children
+ .find { it.className == TextView::class.java.name }
+ .assertNotNull { "Empty device list" }
+ }, 30_000)
device!!.click()
eventually {
@@ -174,4 +198,46 @@
private fun click(label: String) {
waitFindObject(byTextIgnoreCase(label)).click()
waitForIdle()
+}
+
+fun hasClassThat(condition: Matcher<in String?>?): Matcher<AccessibilityNodeInfo> {
+ return MatcherUtils.propertyMatches(
+ "class",
+ { obj: AccessibilityNodeInfo -> obj.className },
+ condition)
+}
+
+fun bundleOf(vararg entries: Pair<String, Any>) = Bundle().apply {
+ entries.forEach { (k, v) -> set(k, v) }
+}
+
+operator fun Bundle.set(key: String, value: Any?) {
+ if (value is Array<*> && value.isArrayOf<Parcelable>()) {
+ putParcelableArray(key, value as Array<Parcelable>)
+ return
+ }
+ if (value is Array<*> && value.isArrayOf<CharSequence>()) {
+ putCharSequenceArray(key, value as Array<CharSequence>)
+ return
+ }
+ when (value) {
+ is Byte -> putByte(key, value)
+ is Char -> putChar(key, value)
+ is Short -> putShort(key, value)
+ is Float -> putFloat(key, value)
+ is CharSequence -> putCharSequence(key, value)
+ is Parcelable -> putParcelable(key, value)
+ is Size -> putSize(key, value)
+ is SizeF -> putSizeF(key, value)
+ is ArrayList<*> -> putParcelableArrayList(key, value as ArrayList<Parcelable>)
+ is SparseArray<*> -> putSparseParcelableArray(key, value as SparseArray<Parcelable>)
+ is Serializable -> putSerializable(key, value)
+ is ByteArray -> putByteArray(key, value)
+ is ShortArray -> putShortArray(key, value)
+ is CharArray -> putCharArray(key, value)
+ is FloatArray -> putFloatArray(key, value)
+ is Bundle -> putBundle(key, value)
+ is Binder -> putBinder(key, value)
+ else -> throw IllegalArgumentException("" + value)
+ }
}
\ No newline at end of file
diff --git a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
index 66b0ca3..0b0f5e4 100644
--- a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
+++ b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
@@ -18,11 +18,19 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.os.Parcel;
import android.os.VibrationEffect;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -37,10 +45,14 @@
public class VibrationEffectTest {
private static final long TEST_TIMING = 100;
private static final int TEST_AMPLITUDE = 100;
+ private static final float TEST_FLOAT_AMPLITUDE = TEST_AMPLITUDE / 255f;
+ private static final float TEST_TOLERANCE = 1e-5f;
- private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 };
+ private static final long[] TEST_TIMINGS = new long[]{100, 100, 200};
private static final int[] TEST_AMPLITUDES =
- new int[] { 255, 0, VibrationEffect.DEFAULT_AMPLITUDE };
+ new int[]{255, 0, VibrationEffect.DEFAULT_AMPLITUDE};
+ private static final float[] TEST_FLOAT_AMPLITUDES =
+ new float[]{1f, 0f, VibrationEffect.DEFAULT_AMPLITUDE};
private static final VibrationEffect TEST_ONE_SHOT =
VibrationEffect.createOneShot(TEST_TIMING, TEST_AMPLITUDE);
@@ -48,11 +60,23 @@
VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
private static final VibrationEffect TEST_WAVEFORM_NO_AMPLITUDES =
VibrationEffect.createWaveform(TEST_TIMINGS, -1);
+ private static final VibrationEffect TEST_WAVEFORM_BUILT =
+ VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+ .addStep(/* amplitude= */ 0.8f, /* frequency= */ -1f, /* duration= */ 20)
+ .addRamp(/* amplitude= */ 1f, /* duration= */ 100)
+ .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 1f, /* duration= */ 200)
+ .build();
private static final VibrationEffect TEST_PREBAKED =
VibrationEffect.get(VibrationEffect.EFFECT_CLICK, true);
private static final VibrationEffect TEST_COMPOSED =
VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.8f)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, /* delay= */ 10)
+ .addEffect(TEST_ONE_SHOT)
+ .addEffect(TEST_WAVEFORM, /* delay= */ 10)
+ .addEffect(TEST_WAVEFORM_BUILT, /* delay= */ 100)
.compose();
@@ -60,40 +84,32 @@
public void testCreateOneShot() {
VibrationEffect e = VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
assertEquals(100, e.getDuration());
- assertEquals(VibrationEffect.DEFAULT_AMPLITUDE,
- ((VibrationEffect.OneShot)e).getAmplitude());
+ assertAmplitude(VibrationEffect.DEFAULT_AMPLITUDE, e, 0);
+
e = VibrationEffect.createOneShot(1, 1);
assertEquals(1, e.getDuration());
- assertEquals(1, ((VibrationEffect.OneShot)e).getAmplitude());
+ assertAmplitude(1 / 255f, e, 0);
+
e = VibrationEffect.createOneShot(1000, 255);
assertEquals(1000, e.getDuration());
- assertEquals(255, ((VibrationEffect.OneShot)e).getAmplitude());
+ assertAmplitude(1f, e, 0);
}
- @Test
+ @Test(expected = IllegalArgumentException.class)
public void testCreateOneShotFailsBadTiming() {
- try {
- VibrationEffect.createOneShot(0, TEST_AMPLITUDE);
- fail("Invalid timing, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ VibrationEffect.createOneShot(0, TEST_AMPLITUDE);
}
@Test
public void testCreateOneShotFailsBadAmplitude() {
- try {
- VibrationEffect.createOneShot(TEST_TIMING, -2);
- fail("Invalid amplitude, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createOneShot(TEST_TIMING, -2));
- try {
- VibrationEffect.createOneShot(TEST_TIMING, 0);
- fail("Invalid amplitude, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createOneShot(TEST_TIMING, 0));
- try {
- VibrationEffect.createOneShot(TEST_TIMING, 256);
- fail("Invalid amplitude, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createOneShot(TEST_TIMING, 256));
}
@Test
@@ -136,11 +152,10 @@
boolean[] fallbacks = { false, true };
for (int id : ids) {
for (boolean fallback : fallbacks) {
- VibrationEffect.Prebaked effect = (VibrationEffect.Prebaked)
- VibrationEffect.get(id, fallback);
- assertEquals(id, effect.getId());
- assertEquals(fallback, effect.shouldFallback());
+ VibrationEffect effect = VibrationEffect.get(id, fallback);
assertEquals(-1, effect.getDuration());
+ assertPrebakedEffectId(id, effect, 0);
+ assertShouldFallback(fallback, effect, 0);
}
}
}
@@ -164,110 +179,92 @@
@Test
public void testCreateWaveform() {
- VibrationEffect.Waveform effect = (VibrationEffect.Waveform)
- VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
- assertArrayEquals(TEST_TIMINGS, effect.getTimings());
- assertArrayEquals(TEST_AMPLITUDES, effect.getAmplitudes());
- assertEquals(-1, effect.getRepeatIndex());
+ VibrationEffect effect = VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
+ assertArrayEquals(TEST_TIMINGS, getTimings(effect));
+ assertEquals(-1, getRepeatIndex(effect));
assertEquals(400, effect.getDuration());
- effect = (VibrationEffect.Waveform)
- VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0);
- assertArrayEquals(TEST_TIMINGS, effect.getTimings());
- assertArrayEquals(TEST_AMPLITUDES, effect.getAmplitudes());
- assertEquals(0, effect.getRepeatIndex());
- assertEquals(Long.MAX_VALUE, effect.getDuration());
- effect = (VibrationEffect.Waveform)VibrationEffect.createWaveform(TEST_TIMINGS,
- TEST_AMPLITUDES, TEST_AMPLITUDES.length - 1);
- assertArrayEquals(TEST_TIMINGS, effect.getTimings());
- assertArrayEquals(TEST_AMPLITUDES, effect.getAmplitudes());
- assertEquals(TEST_AMPLITUDES.length - 1, effect.getRepeatIndex());
- assertEquals(Long.MAX_VALUE, effect.getDuration());
+ for (int i = 0; i < TEST_TIMINGS.length; i++) {
+ assertAmplitude(TEST_FLOAT_AMPLITUDES[i], effect, i);
+ }
+
+ effect = VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, 0);
+ assertEquals(0, getRepeatIndex(effect));
+
+ effect = VibrationEffect.createWaveform(
+ TEST_TIMINGS, TEST_AMPLITUDES, TEST_AMPLITUDES.length - 1);
+ assertEquals(TEST_AMPLITUDES.length - 1, getRepeatIndex(effect));
}
@Test
public void testCreateWaveformFailsDifferentArraySize() {
- try {
- VibrationEffect.createWaveform(
- Arrays.copyOfRange(TEST_TIMINGS, 0, TEST_TIMINGS.length - 1),
- TEST_AMPLITUDES, -1);
- fail("Timing and amplitudes arrays are different sizes, " +
- "should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(
+ Arrays.copyOfRange(TEST_TIMINGS, 0, TEST_TIMINGS.length - 1),
+ TEST_AMPLITUDES, -1));
- try {
- VibrationEffect.createWaveform(
- TEST_TIMINGS,
- Arrays.copyOfRange(TEST_AMPLITUDES, 0, TEST_AMPLITUDES.length - 1), -1);
- fail("Timing and amplitudes arrays are different sizes, " +
- "should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(TEST_TIMINGS,
+ Arrays.copyOfRange(TEST_AMPLITUDES, 0, TEST_AMPLITUDES.length - 1), -1));
}
@Test
public void testCreateWaveformFailsRepeatIndexOutOfBounds() {
- try {
- VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -2);
- fail("Repeat index is < -1, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -2));
- try {
- VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, TEST_AMPLITUDES.length);
- fail("Repeat index is >= array length, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES,
+ TEST_AMPLITUDES.length));
}
@Test
public void testCreateWaveformFailsBadTimingValues() {
- try {
- final long[] badTimings = Arrays.copyOf(TEST_TIMINGS, TEST_TIMINGS.length);
- badTimings[1] = -1;
- VibrationEffect.createWaveform(badTimings,TEST_AMPLITUDES, -1);
- fail("Has a timing < 0, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ final long[] badTimings = Arrays.copyOf(TEST_TIMINGS, TEST_TIMINGS.length);
+ badTimings[1] = -1;
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(badTimings,TEST_AMPLITUDES, -1));
- try {
- final long[] badTimings = new long[TEST_TIMINGS.length];
- VibrationEffect.createWaveform(badTimings, TEST_AMPLITUDES, -1);
- fail("Has no non-zero timings, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ final long[] emptyTimings = new long[TEST_TIMINGS.length];
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(emptyTimings, TEST_AMPLITUDES, -1));
}
@Test
public void testCreateWaveformFailsBadAmplitudeValues() {
- try {
- final int[] badAmplitudes = new int[TEST_TIMINGS.length];
- badAmplitudes[1] = -2;
- VibrationEffect.createWaveform(TEST_TIMINGS, badAmplitudes, -1);
- fail("Has an amplitude < VibrationEffect.DEFAULT_AMPLITUDE, " +
- "should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ final int[] negativeAmplitudes = new int[TEST_TIMINGS.length];
+ negativeAmplitudes[1] = -2;
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(TEST_TIMINGS, negativeAmplitudes, -1));
- try {
- final int[] badAmplitudes = new int[TEST_TIMINGS.length];
- badAmplitudes[1] = 256;
- VibrationEffect.createWaveform(TEST_TIMINGS, badAmplitudes, -1);
- fail("Has an amplitude > 255, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ final int[] highAmplitudes = new int[TEST_TIMINGS.length];
+ highAmplitudes[1] = 256;
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(TEST_TIMINGS, highAmplitudes, -1));
}
@Test
public void testCreateWaveformWithNoAmplitudes() {
- VibrationEffect.createWaveform(TEST_TIMINGS, -1);
- VibrationEffect.createWaveform(TEST_TIMINGS, 0);
- VibrationEffect.createWaveform(TEST_TIMINGS, TEST_TIMINGS.length - 1);
+ VibrationEffect effect = VibrationEffect.createWaveform(TEST_TIMINGS, -1);
+ assertArrayEquals(TEST_TIMINGS, getTimings(effect));
+ assertEquals(-1, getRepeatIndex(effect));
+ for (int i = 0; i < TEST_TIMINGS.length; i++) {
+ assertAmplitude(i % 2 == 0 ? 0 : VibrationEffect.DEFAULT_AMPLITUDE, effect, i);
+ }
+
+ effect = VibrationEffect.createWaveform(TEST_TIMINGS, 0);
+ assertEquals(0, getRepeatIndex(effect));
+
+ effect = VibrationEffect.createWaveform(TEST_TIMINGS, TEST_TIMINGS.length - 1);
+ assertEquals(TEST_TIMINGS.length - 1, getRepeatIndex(effect));
}
@Test
public void testCreateWaveformWithNoAmplitudesFailsRepeatIndexOutOfBounds() {
- try {
- VibrationEffect.createWaveform(TEST_TIMINGS, -2);
- fail("Repeat index is < -1, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(TEST_TIMINGS, -2));
- try {
- VibrationEffect.createWaveform(TEST_TIMINGS, TEST_TIMINGS.length);
- fail("Repeat index is >= timings array length, should throw IllegalArgumentException");
- } catch (IllegalArgumentException expected) { }
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.createWaveform(TEST_TIMINGS, TEST_TIMINGS.length));
}
@Test
@@ -382,7 +379,6 @@
public void testDescribeContents() {
TEST_ONE_SHOT.describeContents();
TEST_WAVEFORM.describeContents();
- TEST_WAVEFORM_NO_AMPLITUDES.describeContents();
TEST_PREBAKED.describeContents();
TEST_COMPOSED.describeContents();
}
@@ -395,16 +391,43 @@
}
@Test
+ public void testComposed() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addEffect(TEST_ONE_SHOT)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
+ .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_THUD))
+ .addEffect(TEST_WAVEFORM)
+ .compose();
+
+ assertEquals(-1, effect.getDuration());
+ assertArrayEquals(new long[]{
+ -1 /* tick */, TEST_TIMING /* oneshot */, -1 /* click */, -1 /* thud */,
+ 100, 100, 200 /* waveform */
+ }, getTimings(effect));
+ assertPrimitiveId(VibrationEffect.Composition.PRIMITIVE_TICK, effect, 0);
+ assertAmplitude(TEST_FLOAT_AMPLITUDE, effect, 1);
+ assertPrimitiveId(VibrationEffect.Composition.PRIMITIVE_CLICK, effect, 2);
+ assertPrebakedEffectId(VibrationEffect.EFFECT_THUD, effect, 3);
+ assertAmplitude(TEST_FLOAT_AMPLITUDES[0], effect, 4);
+ assertAmplitude(TEST_FLOAT_AMPLITUDES[1], effect, 5);
+ assertAmplitude(TEST_FLOAT_AMPLITUDES[2], effect, 6);
+ }
+
+ @Test
public void testComposedEquals() {
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL)
+ .addEffect(TEST_ONE_SHOT, /* delay= */ 10)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 10)
+ .addEffect(TEST_WAVEFORM)
.compose();
+
VibrationEffect otherEffect = VibrationEffect.startComposition()
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f)
- .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 1f, 0)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 0)
+ .addEffect(TEST_ONE_SHOT, /* delay= */ 10)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 10)
+ .addEffect(TEST_WAVEFORM)
.compose();
assertEquals(effect, otherEffect);
assertEquals(effect.hashCode(), otherEffect.hashCode());
@@ -469,20 +492,196 @@
}
@Test
+ public void testComposedDifferentWaveformsNotEquals() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addEffect(TEST_ONE_SHOT)
+ .compose();
+ VibrationEffect otherEffect = VibrationEffect.startComposition()
+ .addEffect(TEST_WAVEFORM)
+ .compose();
+ assertNotEquals(effect, otherEffect);
+ }
+
+ @Test
+ public void testComposedDifferentWaveformDelayNotEquals() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addEffect(TEST_ONE_SHOT, /* delay= */ 10)
+ .compose();
+ VibrationEffect otherEffect = VibrationEffect.startComposition()
+ .addEffect(TEST_ONE_SHOT, /* delay= */ 100)
+ .compose();
+ assertNotEquals(effect, otherEffect);
+ }
+
+ @Test
public void testComposedDuration() {
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1000)
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addEffect(TEST_ONE_SHOT)
.compose();
assertEquals(-1, effect.getDuration());
+
+ effect = VibrationEffect.startComposition()
+ .addEffect(TEST_ONE_SHOT)
+ .compose();
+ assertEquals(TEST_ONE_SHOT.getDuration(), effect.getDuration());
+
+ effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addEffect(VibrationEffect.createWaveform(new long[]{10, 10}, /* repeat= */ 0))
+ .compose();
+ assertEquals(Long.MAX_VALUE, effect.getDuration());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testComposeEmptyCompositionIsInvalid() {
+ VibrationEffect.startComposition().compose();
}
@Test
- public void testComposeEmptyCompositionIsInvalid() {
- try {
- VibrationEffect.startComposition().compose();
- fail("Illegal composition, should throw IllegalStateException");
- } catch (IllegalStateException expected) {}
+ public void testStartWaveform() {
+ VibrationEffect.WaveformBuilder first = VibrationEffect.startWaveform();
+ VibrationEffect.WaveformBuilder other = VibrationEffect.startWaveform();
+ assertNotEquals(first, other);
+
+ VibrationEffect effect = VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+ .addStep(/* amplitude= */ 0.8f, /* frequency= */ -1f, /* duration= */ 20)
+ .addRamp(/* amplitude= */ 1f, /* duration= */ 100)
+ .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 1f, /* duration= */ 200)
+ .build();
+
+ assertArrayEquals(new long[]{10, 20, 100, 200}, getTimings(effect));
+ assertStepSegment(effect, 0);
+ assertAmplitude(0.5f, effect, 0);
+ assertFrequency(0f, effect, 0);
+
+ assertStepSegment(effect, 1);
+ assertAmplitude(0.8f, effect, 1);
+ assertFrequency(-1f, effect, 1);
+
+ assertRampSegment(effect, 2);
+ assertAmplitude(1f, effect, 2);
+ assertFrequency(-1f, effect, 2);
+
+ assertRampSegment(effect, 3);
+ assertAmplitude(0.2f, effect, 3);
+ assertFrequency(1f, effect, 3);
+ }
+
+ @Test
+ public void testStartWaveformEquals() {
+ VibrationEffect other = VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+ .addStep(/* amplitude= */ 0.8f, /* frequency= */ -1f, /* duration= */ 20)
+ .addRamp(/* amplitude= */ 1f, /* duration= */ 100)
+ .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 1f, /* duration= */ 200)
+ .build();
+ assertEquals(TEST_WAVEFORM_BUILT, other);
+ assertEquals(TEST_WAVEFORM_BUILT.hashCode(), other.hashCode());
+
+ VibrationEffect.WaveformBuilder builder = VibrationEffect.startWaveform()
+ .addStep(TEST_FLOAT_AMPLITUDE, (int) TEST_TIMING);
+ assertEquals(TEST_ONE_SHOT, builder.build());
+ assertEquals(TEST_ONE_SHOT.hashCode(), builder.build().hashCode());
+
+ builder = VibrationEffect.startWaveform();
+ for (int i = 0; i < TEST_TIMINGS.length; i++) {
+ builder.addStep(i % 2 == 0 ? 0 : VibrationEffect.DEFAULT_AMPLITUDE,
+ (int) TEST_TIMINGS[i]);
+ }
+ assertEquals(TEST_WAVEFORM_NO_AMPLITUDES, builder.build());
+ assertEquals(TEST_WAVEFORM_NO_AMPLITUDES.hashCode(), builder.build().hashCode());
+
+ builder = VibrationEffect.startWaveform();
+ for (int i = 0; i < TEST_TIMINGS.length; i++) {
+ builder.addStep(TEST_FLOAT_AMPLITUDES[i], (int) TEST_TIMINGS[i]);
+ }
+ assertEquals(TEST_WAVEFORM, builder.build());
+ assertEquals(TEST_WAVEFORM.hashCode(), builder.build().hashCode());
+ }
+
+ @Test
+ public void testStartWaveformNotEqualsDifferentNumberOfSteps() {
+ VibrationEffect other = VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+ .addRamp(/* amplitude= */ 1f, /* duration= */ 100)
+ .build();
+ assertNotEquals(TEST_WAVEFORM_BUILT, other);
+ }
+
+ @Test
+ public void testStartWaveformNotEqualsDifferentTypesOfStep() {
+ VibrationEffect first = VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+ .build();
+ VibrationEffect second = VibrationEffect.startWaveform()
+ .addRamp(/* amplitude= */ 0.5f, /* duration= */ 10)
+ .build();
+ assertNotEquals(first, second);
+ }
+
+ @Test
+ public void testStartWaveformNotEqualsDifferentRepeatIndex() {
+ VibrationEffect first = VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+ .build(0);
+ VibrationEffect second = VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ 1f, /* duration= */ 10)
+ .build(-1);
+ assertNotEquals(first, second);
+ }
+
+ @Test
+ public void testStartWaveformNotEqualsDifferentAmplitudes() {
+ VibrationEffect first = VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
+ .build();
+ VibrationEffect second = VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ 1f, /* duration= */ 10)
+ .build();
+ assertNotEquals(first, second);
+ }
+
+ @Test
+ public void testStartWaveformNotEqualsDifferentFrequency() {
+ VibrationEffect first = VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ 0.5f, /* frequency= */ 0.5f, /* duration= */ 10)
+ .build();
+ VibrationEffect second = VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */ 0.5f, /* frequency= */ -1f, /* duration= */ 10)
+ .build();
+ assertNotEquals(first, second);
+ }
+
+ @Test
+ public void testStartWaveformNotEqualsDifferentDuration() {
+ VibrationEffect first = VibrationEffect.startWaveform()
+ .addRamp(/* amplitude= */ 0.5f, /* duration= */ 1)
+ .build();
+ VibrationEffect second = VibrationEffect.startWaveform()
+ .addRamp(/* amplitude= */ 0.5f, /* duration= */ 10)
+ .build();
+ assertNotEquals(first, second);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testStartWaveformEmptyBuilderIsInvalid() {
+ VibrationEffect.startWaveform().build();
+ }
+
+ @Test
+ public void testStartWaveformFailsRepeatIndexOutOfBounds() {
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */1, /* duration= */ 20)
+ .build(-2));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> VibrationEffect.startWaveform()
+ .addStep(/* amplitude= */1, /* duration= */ 20)
+ .build(1));
}
@Test
@@ -492,4 +691,81 @@
TEST_PREBAKED.toString();
TEST_COMPOSED.toString();
}
+
+ private long[] getTimings(VibrationEffect effect) {
+ return ((VibrationEffect.Composed) effect).getSegments().stream()
+ .mapToLong(VibrationEffectSegment::getDuration)
+ .toArray();
+ }
+
+ private int getRepeatIndex(VibrationEffect effect) {
+ return ((VibrationEffect.Composed) effect).getRepeatIndex();
+ }
+
+ private void assertStepSegment(VibrationEffect effect, int index) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ assertTrue(index < composed.getSegments().size());
+ assertTrue(composed.getSegments().get(index) instanceof StepSegment);
+ }
+
+ private void assertRampSegment(VibrationEffect effect, int index) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ assertTrue(index < composed.getSegments().size());
+ assertTrue(composed.getSegments().get(index) instanceof RampSegment);
+ }
+
+ private void assertAmplitude(float expected, VibrationEffect effect, int index) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ assertTrue(index < composed.getSegments().size());
+ VibrationEffectSegment segment = composed.getSegments().get(index);
+ if (segment instanceof StepSegment) {
+ assertEquals(expected, ((StepSegment) composed.getSegments().get(index)).getAmplitude(),
+ TEST_TOLERANCE);
+ } else if (segment instanceof RampSegment) {
+ assertEquals(expected,
+ ((RampSegment) composed.getSegments().get(index)).getEndAmplitude(),
+ TEST_TOLERANCE);
+ } else {
+ fail("Expected a step or ramp segment at index " + index + " of " + effect);
+ }
+ }
+
+ private void assertFrequency(float expected, VibrationEffect effect, int index) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ assertTrue(index < composed.getSegments().size());
+ VibrationEffectSegment segment = composed.getSegments().get(index);
+ if (segment instanceof StepSegment) {
+ assertEquals(expected, ((StepSegment) composed.getSegments().get(index)).getFrequency(),
+ TEST_TOLERANCE);
+ } else if (segment instanceof RampSegment) {
+ assertEquals(expected,
+ ((RampSegment) composed.getSegments().get(index)).getEndFrequency(),
+ TEST_TOLERANCE);
+ } else {
+ fail("Expected a step or ramp segment at index " + index + " of " + effect);
+ }
+ }
+
+ private void assertPrebakedEffectId(int expected, VibrationEffect effect, int index) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ assertTrue(index < composed.getSegments().size());
+ assertTrue(composed.getSegments().get(index) instanceof PrebakedSegment);
+ assertEquals(expected, ((PrebakedSegment) composed.getSegments().get(index)).getEffectId());
+ }
+
+ private void assertShouldFallback(boolean expected, VibrationEffect effect, int index) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ assertTrue(index < composed.getSegments().size());
+ assertTrue(composed.getSegments().get(index) instanceof PrebakedSegment);
+ assertEquals(expected,
+ ((PrebakedSegment) composed.getSegments().get(index)).shouldFallback());
+ }
+
+ private void assertPrimitiveId(int expected, VibrationEffect effect, int index) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
+ assertTrue(index < composed.getSegments().size());
+ assertTrue(composed.getSegments().get(index) instanceof PrimitiveSegment);
+ assertEquals(expected,
+ ((PrimitiveSegment) composed.getSegments().get(index)).getPrimitiveId());
+ }
}
diff --git a/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
index 3923c35..e60e53a 100644
--- a/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
+++ b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.PackageManager;
import android.content.pm.PackageInstaller
import android.content.pm.PackageInstaller.EXTRA_STATUS
import android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID
@@ -38,6 +39,12 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.junit.runners.model.Statement
+import org.junit.runner.Description
+import org.junit.Rule
+import org.junit.rules.TestRule
+import org.junit.AssumptionViolatedException
+
import java.io.File
import java.lang.IllegalArgumentException
@@ -62,6 +69,7 @@
private var packageName = context.packageName
private var apkFile = File(context.filesDir, TEST_APK_NAME)
private var uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ private var isWatch: Boolean = pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
@@ -75,6 +83,21 @@
}
}
+ @get:Rule
+ val testRule: TestRule = object :TestRule {
+ override fun apply(base: Statement, description: Description?)
+ = NewStatement(base)
+ inner class NewStatement(private val base: Statement) : Statement() {
+ @Throws(Throwable::class)
+ override fun evaluate() {
+ if (isWatch) {
+ throw AssumptionViolatedException("Install/uninstall feature is not supported on WearOs");
+ }
+ base.evaluate()
+ }
+ }
+ }
+
@Before
fun wakeUpScreen() {
if (!uiDevice.isScreenOn) {
diff --git a/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java b/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java
index e5fda9b..5aa9c5d 100644
--- a/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java
+++ b/tests/tests/packageinstaller/tapjacking/src/android/packageinstaller/tapjacking/cts/TapjackingTest.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.platform.test.annotations.AppModeFull;
import android.support.test.uiautomator.By;
@@ -40,6 +41,11 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.AssumptionViolatedException;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
import java.io.IOException;
@@ -57,13 +63,16 @@
private static final long WAIT_FOR_UI_TIMEOUT = 5000;
- private Context mContext;
+ private Context mContext = InstrumentationRegistry.getTargetContext();
private String mPackageName;
private UiDevice mUiDevice;
+ boolean isWatch = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+
+ @Rule
+ public final RequiredRule mRequiredRule = new RequiredRule(isWatch);
@Before
public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
mPackageName = mContext.getPackageName();
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
if (!mUiDevice.isScreenOn()) {
@@ -114,4 +123,24 @@
public void tearDown() throws Exception {
mUiDevice.pressHome();
}
+
+ private static final class RequiredRule implements TestRule {
+ boolean mIsWatch;
+ RequiredRule(boolean isWatch) {
+ mIsWatch = isWatch;
+ }
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+
+ @Override
+ public void evaluate() throws Throwable {
+ if (mIsWatch) {
+ throw new AssumptionViolatedException("Install/uninstall feature is not supported on WearOs");
+ }
+ base.evaluate();
+ }
+ };
+ }
+ }
}
diff --git a/tests/tests/permission/StorageEscalationApp28/Android.bp b/tests/tests/permission/StorageEscalationApp28/Android.bp
index b59c3eb..520625a 100644
--- a/tests/tests/permission/StorageEscalationApp28/Android.bp
+++ b/tests/tests/permission/StorageEscalationApp28/Android.bp
@@ -25,5 +25,6 @@
"cts",
"general-tests",
"mts",
+ "sts",
],
}
diff --git a/tests/tests/permission/StorageEscalationApp29Full/Android.bp b/tests/tests/permission/StorageEscalationApp29Full/Android.bp
index 66ad07a..018c324 100644
--- a/tests/tests/permission/StorageEscalationApp29Full/Android.bp
+++ b/tests/tests/permission/StorageEscalationApp29Full/Android.bp
@@ -24,5 +24,7 @@
test_suites: [
"cts",
"general-tests",
+ "mts",
+ "sts",
],
}
diff --git a/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp b/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp
index 92beca5..5c0d89a 100644
--- a/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp
+++ b/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp
@@ -24,5 +24,7 @@
test_suites: [
"cts",
"general-tests",
+ "mts",
+ "sts",
],
}
diff --git a/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt b/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
index 00d4b40..0f21318 100644
--- a/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
+++ b/tests/tests/permission/src/android/permission/cts/StorageEscalationTest.kt
@@ -24,6 +24,7 @@
import android.content.Context
import android.content.pm.PackageManager
import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.SecurityTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.compatibility.common.util.SystemUtil
import org.junit.After
@@ -72,11 +73,13 @@
}
@Test
+ @SecurityTest(minPatchLevel = "2021-03")
fun testCannotEscalateWithSdkDowngrade() {
runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_28)
}
@Test
+ @SecurityTest(minPatchLevel = "2021-03")
fun testCannotEscalateWithNewManifestLegacyRequest() {
runStorageEscalationTest(APP_APK_PATH_29_SCOPED, APP_APK_PATH_29_FULL)
}
@@ -90,4 +93,4 @@
Thread.sleep(DELAY_TIME_MS)
assertStoragePermissionState(granted = false)
}
-}
\ 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 27b30bc..7c8435d 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -694,6 +694,7 @@
<!-- Added in S -->
<protected-broadcast android:name="android.intent.action.REBOOT_READY" />
+ <protected-broadcast android:name="android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -987,6 +988,23 @@
android:permissionGroup="android.permission-group.UNDEFINED"
android:protectionLevel="signature|appop|preinstalled" />
+ <!-- Allows an application to modify and delete media files on this device or any connected
+ storage device without user confirmation. Applications must already be granted the
+ {@link #READ_EXTERNAL_STORAGE} or {@link #MANAGE_EXTERNAL_STORAGE}} permissions for this
+ permission to take effect.
+ <p>Even if applications are granted this permission, if applications want to modify or
+ delete media files, they also must get the access by calling
+ {@link android.provider.MediaStore#createWriteRequest(ContentResolver, Collection)},
+ {@link android.provider.MediaStore#createDeleteRequest(ContentResolver, Collection)}, or
+ {@link android.provider.MediaStore#createTrashRequest(ContentResolver, Collection, boolean)}.
+ <p>This permission doesn't give read or write access directly. It only prevents the user
+ confirmation dialog for these requests.
+ <p>If applications are not granted {@link #ACCESS_MEDIA_LOCATION}, the system also pops up
+ the user confirmation dialog for the write request.
+ <p>Protection level: signature|appop|preinstalled -->
+ <permission android:name="android.permission.MANAGE_MEDIA"
+ android:protectionLevel="signature|appop|preinstalled" />
+
<!-- ====================================================================== -->
<!-- Permissions for accessing the device location -->
<!-- ====================================================================== -->
@@ -1370,6 +1388,14 @@
android:backgroundPermission="android.permission.BACKGROUND_CAMERA"
android:protectionLevel="dangerous|instant" />
+ <!-- Required to be able to discover and connect to nearby Bluetooth devices.
+ <p>Protection level: dangerous -->
+ <permission-group android:name="android.permission-group.NEARBY_DEVICES"
+ android:icon="@drawable/ic_qs_bluetooth"
+ android:label="@string/permgrouplab_nearby_devices"
+ android:description="@string/permgroupdesc_nearby_devices"
+ android:priority="750" />
+
<!-- @SystemApi @TestApi Required to be able to access the camera device in the background.
This permission is not intended to be held by apps.
<p>Protection level: internal
@@ -1912,6 +1938,22 @@
android:label="@string/permlab_bluetooth"
android:protectionLevel="normal" />
+ <!-- Required to be able to discover and pair nearby Bluetooth devices.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.BLUETOOTH_SCAN"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_bluetooth_scan"
+ android:label="@string/permlab_bluetooth_scan"
+ android:protectionLevel="dangerous" />
+
+ <!-- Required to be able to connect to paired Bluetooth devices.
+ <p>Protection level: dangerous -->
+ <permission android:name="android.permission.BLUETOOTH_CONNECT"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_bluetooth_connect"
+ android:label="@string/permlab_bluetooth_connect"
+ android:protectionLevel="dangerous" />
+
<!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the
user from using them until they are unsuspended.
@hide
@@ -1945,6 +1987,12 @@
<permission android:name="android.permission.BLUETOOTH_STACK"
android:protectionLevel="signature" />
+ <!-- Allows uhid write access for creating virtual input devices
+ @hide
+ -->
+ <permission android:name="android.permission.VIRTUAL_INPUT_DEVICE"
+ android:protectionLevel="signature" />
+
<!-- Allows applications to perform I/O operations over NFC.
<p>Protection level: normal
-->
@@ -2292,11 +2340,6 @@
<permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
android:protectionLevel="signature|privileged" />
- <!-- @SystemApi Allows read access to privileged network state in the device config.
- @hide Used internally. -->
- <permission android:name="android.permission.READ_NETWORK_DEVICE_CONFIG"
- android:protectionLevel="signature|privileged" />
-
<!-- Allows to read device identifiers and use ICC based authentication like EAP-AKA.
Often required in authentication to access the carrier's server and manage services
of the subscriber.
@@ -2646,10 +2689,6 @@
<permission android:name="android.permission.CREATE_USERS"
android:protectionLevel="signature" />
- <!-- @TestApi @hide Allows an application to query user info for all users on the device. -->
- <permission android:name="android.permission.QUERY_USERS"
- android:protectionLevel="signature" />
-
<!-- @hide Allows an application to set the profile owners and the device owner.
This permission is not available to third party applications.-->
<permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"
@@ -2930,6 +2969,15 @@
<permission android:name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"
android:protectionLevel="signature" />
+ <!-- Allows system clock time suggestions from an external clock / time source to be made.
+ The nature of "external" could be highly form-factor specific. Example, times
+ obtained via the VHAL for Android Auto OS.
+ <p>Not for use by third-party applications.
+ @SystemApi @hide
+ -->
+ <permission android:name="android.permission.SUGGEST_EXTERNAL_TIME"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows applications like settings to manage configuration associated with automatic time
and time zone detection.
<p>Not for use by third-party applications.
@@ -3434,6 +3482,14 @@
<permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
android:protectionLevel="signature" />
+ <!-- Allows an application to avoid all toast rate limiting restrictions.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.UNLIMITED_TOASTS"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.UNLIMITED_TOASTS" />
+
<!-- @SystemApi Allows an application to use
{@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
to hide non-system-overlay windows.
@@ -3537,7 +3593,7 @@
@hide
-->
<permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|recents" />
<!-- Allows an application to retrieve the current state of keys and
switches.
@@ -3690,6 +3746,13 @@
<permission android:name="android.permission.BIND_HOTWORD_DETECTION_SERVICE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to manage hotword detection on the device.
+ <p>Protection level: internal|preinstalled
+ @hide This is not a third-party API (intended for OEMs and system apps).
+ -->
+ <permission android:name="android.permission.MANAGE_HOTWORD_DETECTION"
+ android:protectionLevel="internal|preinstalled" />
+
<!-- Must be required by a {@link android.service.autofill.AutofillService},
to ensure that only the system can bind to it.
<p>Protection level: signature
@@ -4807,11 +4870,9 @@
<!-- @SystemApi @hide Domain verification agent package needs to have this permission before the
system will trust it to verify domains.
-
- TODO(159952358): STOPSHIP: This must be updated to the new "internal" protectionLevel
-->
<permission android:name="android.permission.DOMAIN_VERIFICATION_AGENT"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="internal|privileged" />
<!-- @SystemApi @hide Must be required by the domain verification agent's intent
BroadcastReceiver, to ensure that only the system can interact with it.
@@ -4894,6 +4955,11 @@
<permission android:name="android.permission.SET_INITIAL_LOCK"
android:protectionLevel="signature|setup"/>
+ <!-- @TestApi Allows applications to set and verify lockscreen credentials.
+ @hide -->
+ <permission android:name="android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS"
+ android:protectionLevel="signature"/>
+
<!-- Allows managing (adding, removing) fingerprint templates. Reserved for the system. @hide -->
<permission android:name="android.permission.MANAGE_FINGERPRINT"
android:protectionLevel="signature|privileged" />
@@ -5431,6 +5497,8 @@
intents}.
<p>Protection level: normal -->
<permission android:name="android.permission.USE_FULL_SCREEN_INTENT"
+ android:label="@string/permlab_fullScreenIntent"
+ android:description="@string/permdesc_fullScreenIntent"
android:protectionLevel="normal" />
<!-- @SystemApi Allows requesting the framework broadcast the
@@ -5531,6 +5599,10 @@
<permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
android:protectionLevel="signature|appPredictor" />
+ <!-- @hide @SystemApi Allows an application to manage app hibernation state. -->
+ <permission android:name="android.permission.MANAGE_APP_HIBERNATION"
+ android:protectionLevel="signature|installer" />
+
<!-- @hide @TestApi Allows apps to reset the state of {@link com.android.server.am.AppErrors}.
<p>CTS tests will use UiAutomation.adoptShellPermissionIdentity() to gain access. -->
<permission android:name="android.permission.RESET_APP_ERRORS"
@@ -5612,6 +5684,10 @@
<!-- Attribution for Gnss Time Update service. -->
<attribution android:tag="GnssTimeUpdateService"
android:label="@string/gnss_time_update_service"/>
+ <!-- Attribution for MusicRecognitionManagerService.
+ <p>Not for use by third-party applications.</p> -->
+ <attribution android:tag="MusicRecognitionManagerService"
+ android:label="@string/music_recognition_manager_service"/>
<application android:process="system"
android:persistent="true"
diff --git a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
index c54a96c..56c0f92 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
+++ b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
@@ -22,6 +22,8 @@
import android.Manifest.permission.ACTIVITY_RECOGNITION
import android.Manifest.permission.ADD_VOICEMAIL
import android.Manifest.permission.ANSWER_PHONE_CALLS
+import android.Manifest.permission.BLUETOOTH_CONNECT
+import android.Manifest.permission.BLUETOOTH_SCAN
import android.Manifest.permission.BODY_SENSORS
import android.Manifest.permission.CALL_PHONE
import android.Manifest.permission.CAMERA
@@ -143,6 +145,11 @@
// runtime permission
expectedPerms.add(ACTIVITY_RECOGNITION)
+ // Add runtime permissions added in S which were _not_ split from a previously existing
+ // runtime permission
+ expectedPerms.add(BLUETOOTH_CONNECT)
+ expectedPerms.add(BLUETOOTH_SCAN)
+
assertThat(expectedPerms).containsExactlyElementsIn(platformRuntimePerms.map { it.name })
}
}
diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp
index 8053fe2..401bd07 100644
--- a/tests/tests/permission3/Android.bp
+++ b/tests/tests/permission3/Android.bp
@@ -26,6 +26,7 @@
],
static_libs: [
"kotlin-stdlib",
+ "androidx.core_core",
"androidx.test.rules",
"compatibility-device-util-axt",
"ctstestrunner-axt",
diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
index 4ee3b21..acecc42 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
@@ -436,7 +436,12 @@
// Automotive doesn't support one time permissions, and thus
// won't show an "Ask every time" message
when (state) {
- PermissionState.ALLOWED -> R.string.allow
+ PermissionState.ALLOWED ->
+ if (showsForegroundOnlyButton(permission)) {
+ R.string.allow_foreground
+ } else {
+ R.string.allow
+ }
PermissionState.DENIED -> R.string.deny
PermissionState.DENIED_WITH_PREJUDICE -> R.string.deny
}
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest29.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest29.kt
index 5d13748..645b5fb 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest29.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest29.kt
@@ -102,6 +102,7 @@
}
}
+ @FlakyTest
@Test
fun testDenyBackgroundWithPrejudice() {
// Step 1: deny the first time
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionUsageInfoTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionUsageInfoTest.kt
index bce5694..60c39fe 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionUsageInfoTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionUsageInfoTest.kt
@@ -21,15 +21,15 @@
import android.content.ComponentName
import android.content.Intent
import android.location.LocationManager
-import android.os.Build
import android.support.test.uiautomator.By
-import com.android.compatibility.common.util.ApiLevelUtil
+import androidx.core.os.BuildCompat
import com.android.compatibility.common.util.AppOpsUtils.setOpMode
import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
@@ -57,6 +57,13 @@
)
}
+ @Before
+ fun assumeHandheld() {
+ assumeFalse(isAutomotive)
+ assumeFalse(isTv)
+ assumeFalse(isWatch)
+ }
+
@After
fun removeTestLocationProvider() {
locationManager.removeTestProvider(APP_PACKAGE_NAME)
@@ -65,7 +72,7 @@
@Test
fun testLocationProviderPermissionUsageInfo() {
val locationProviderPackageName: String
- if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S) || ApiLevelUtil.codenameEquals("S")) {
+ if (BuildCompat.isAtLeastS()) {
// Add the test app as location provider.
val future = startActivityForFuture(
Intent().apply {
diff --git a/tests/tests/provider/AndroidManifest.xml b/tests/tests/provider/AndroidManifest.xml
index edc6310..e5e41ec 100644
--- a/tests/tests/provider/AndroidManifest.xml
+++ b/tests/tests/provider/AndroidManifest.xml
@@ -48,6 +48,7 @@
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.MANAGE_MEDIA"/>
<application>
<uses-library android:name="android.test.runner"/>
diff --git a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
index 4d5849a..5ad3cf5 100644
--- a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
@@ -461,16 +461,25 @@
return false;
}
+ /**
+ * Gets File corresponding to the uri.
+ * This function assumes that the caller has access to the uri
+ * @param uri uri to get File for
+ * @return File file corresponding to the uri
+ * @throws FileNotFoundException if either the file does not exist or the caller does not have
+ * read access to the file
+ */
public static File getRawFile(Uri uri) throws Exception {
- final String res = ProviderTestUtils.executeShellCommand("content query --uri " + uri
- + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
- + " --projection _data",
- InstrumentationRegistry.getInstrumentation().getUiAutomation());
- final int i = res.indexOf("_data=");
- if (i >= 0) {
- return new File(res.substring(i + 6));
+ String filePath;
+ try (Cursor c = InstrumentationRegistry.getTargetContext().getContentResolver().query(uri,
+ new String[] { MediaColumns.DATA }, null, null)) {
+ assertTrue(c.moveToFirst());
+ filePath = c.getString(0);
+ }
+ if (filePath != null) {
+ return new File(filePath);
} else {
- throw new FileNotFoundException("Failed to find _data for " + uri + "; found " + res);
+ throw new FileNotFoundException("Failed to find _data for " + uri);
}
}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
index 1e9ac55..949b691 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.Manifest;
import android.app.AppOpsManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -264,7 +265,25 @@
.isFalse();
}
- public void setAppOpsModeForUid(int uid, int mode, @NonNull String... ops) {
+ @Test
+ public void testCanManageMedia() throws Exception {
+ final String opString = AppOpsManager.permissionToOp(Manifest.permission.MANAGE_MEDIA);
+
+ // no access
+ assertThat(MediaStore.canManageMedia(getContext())).isFalse();
+ try {
+ // grant access
+ setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ALLOWED, opString);
+
+ assertThat(MediaStore.canManageMedia(getContext())).isTrue();
+ } finally {
+ setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ERRORED, opString);
+ }
+ // no access
+ assertThat(MediaStore.canManageMedia(getContext())).isFalse();
+ }
+
+ private void setAppOpsModeForUid(int uid, int mode, @NonNull String... ops) {
getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(null);
try {
for (String op : ops) {
diff --git a/tests/tests/security/native/verified_boot/VerifiedBootTest.cpp b/tests/tests/security/native/verified_boot/VerifiedBootTest.cpp
index ad089a2..0cc60e8 100644
--- a/tests/tests/security/native/verified_boot/VerifiedBootTest.cpp
+++ b/tests/tests/security/native/verified_boot/VerifiedBootTest.cpp
@@ -57,6 +57,12 @@
continue;
}
+ if (android::base::EqualsIgnoreCase(entry.fs_type, "emmc")) {
+ GTEST_LOG_(INFO) << entry.mount_point << " has emmc fs_type, skipping"
+ << " hashtree algorithm verification";
+ continue;
+ }
+
GTEST_LOG_(ERROR) << "partition enabled verity " << entry.mount_point;
// The verity sysprop use "system" as the partition name in the system as
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java
index fd2af3a..bc1ed91 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/IntroActivity.java
@@ -2,30 +2,23 @@
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.UiAutomation;
import android.app.admin.DevicePolicyManager;
+import android.app.admin.ManagedProfileProvisioningParams;
+import android.app.admin.ProvisioningException;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
-import android.view.View;
-import android.util.Log;
-import android.os.SystemClock;
-
-//import android.support.test.InstrumentationRegistry;
-import androidx.test.InstrumentationRegistry;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import java.io.*;
-import java.util.stream.Collectors;
-
+import android.os.UserHandle;
import android.security.cts.CVE_2021_0327.workprofilesetup.AdminReceiver;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
public class IntroActivity extends Activity {
- private static final int AR_WORK_PROFILE_SETUP = 1;
private static final String TAG = "CVE_2021_0327";
private void launchOtherUserActivity() {
@@ -45,50 +38,7 @@
} else if (canLaunchOtherUserActivity()) {
launchOtherUserActivity();
} else {
- setupWorkProfile(null);
-
- //detect buttons to click
- boolean profileSetUp=false;
- String button;
- java.util.List<UiObject2> objects;
- UiDevice mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- BySelector selector = By.clickable(true);
-
-
- while(!profileSetUp){
- do {
- Log.i(TAG, "waiting for clickable");
- SystemClock.sleep(3000);
- } while((objects = mUiDevice.findObjects(selector)).size()==0);
- for(UiObject2 o : objects){
- button=o.getText();
- Log.d(TAG,"button:" + button);
-
- if(button==null){
- continue;
- }
-
- switch(button){
- case "Delete" :
- o.click();
- Log.i(TAG, "clicked: Delete");
- break;
- case "Accept & continue" :
- o.click();
- Log.i(TAG, "clicked: Accept & continue");
- break;
- case "Next" :
- o.click();
- profileSetUp=true;
- Log.i(TAG, "clicked: Next");
- break;
- default :
- continue;
- }
- break;
- }
- }
- //end while(!profileSetUp);
+ setupWorkProfile();
}
}
@@ -101,29 +51,32 @@
return (getPackageManager().resolveActivity(intent, 0) != null);
}
- public void setupWorkProfile(View view) {
+ private void setupWorkProfile() {
Log.d(TAG, "setupWorkProfile()");
- Intent intent = new Intent(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE);
- intent.putExtra(
- DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
- new ComponentName(this, AdminReceiver.class)
- );
- startActivityForResult(intent, AR_WORK_PROFILE_SETUP);
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ uiAutomation.adoptShellPermissionIdentity();
+ DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class);
+ try {
+ UserHandle profile = devicePolicyManager.createAndProvisionManagedProfile(
+ new ManagedProfileProvisioningParams.Builder(
+ new ComponentName(this, AdminReceiver.class),
+ "profileOwner").build());
+ if (profile == null) {
+ showErrorDialog();
+ } else {
+ launchOtherUserActivity();
+ }
+ } catch (ProvisioningException e) {
+ showErrorDialog();
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
}
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- Log.d(TAG, "onActivityResult()");
- if (requestCode == AR_WORK_PROFILE_SETUP) {
- if (resultCode == RESULT_OK) {
- launchOtherUserActivity();
- } else {
- new AlertDialog.Builder(this)
- .setMessage("Work profile setup failed")
- .setPositiveButton("ok", null)
- .show();
- }
- }
- super.onActivityResult(requestCode, resultCode, data);
+ private void showErrorDialog() {
+ new AlertDialog.Builder(this)
+ .setMessage("Work profile setup failed")
+ .setPositiveButton("ok", null)
+ .show();
}
}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0394.java b/tests/tests/security/src/android/security/cts/CVE_2021_0394.java
index d3278ee..6d504f6 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0394.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0394.java
@@ -16,12 +16,12 @@
package android.security.cts;
-import static org.junit.Assert.assertFalse;
-
import android.platform.test.annotations.SecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import androidx.test.filters.RequiresDevice;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import static org.junit.Assert.assertFalse;
@RunWith(AndroidJUnit4.class)
public class CVE_2021_0394 {
@@ -34,6 +34,9 @@
*/
@SecurityTest(minPatchLevel = "2021-03")
@Test
+ @RequiresDevice
+ // emulators always have checkJNI enabled which causes the test
+ // to abort the VM while passing invalid input to NewStringUTF
public void testPocCVE_2021_0394() throws Exception {
assertFalse(poc());
}
diff --git a/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt b/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
index 117b350..c723f26 100644
--- a/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
+++ b/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
@@ -80,7 +80,9 @@
@Test
fun testDialog() {
setSensor(true)
- val intent = Intent(MIC_CAM_ACTIVITY_ACTION).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ val intent = Intent(MIC_CAM_ACTIVITY_ACTION)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_MATCH_EXTERNAL)
for (extra in extras) {
intent.putExtra(extra, true)
}
@@ -89,7 +91,12 @@
SystemUtil.eventually {
assertFalse(isSensorPrivacyEnabled())
}
- context.sendBroadcast(Intent(FINISH_MIC_CAM_ACTIVITY_ACTION))
+
+ // instant apps can't broadcast to other instant apps; use the shell
+ runShellCommandOrThrow("am broadcast" +
+ " --user ${context.userId}" +
+ " -a $FINISH_MIC_CAM_ACTIVITY_ACTION" +
+ " -f ${Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS}")
}
@Test
diff --git a/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraForSensorPrivacy/AndroidManifest.xml b/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraForSensorPrivacy/AndroidManifest.xml
index 490773b..f0ff413 100644
--- a/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraForSensorPrivacy/AndroidManifest.xml
+++ b/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraForSensorPrivacy/AndroidManifest.xml
@@ -17,14 +17,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.sensorprivacy.cts.usemiccamera"
- android:versionCode="1">
+ android:versionCode="1"
+ android:targetSandboxVersion="2">
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>
<application android:label="CtsUseMicOrCameraForSensorPrivacy">
<activity android:name=".UseMicCamera"
- android:exported="true">
+ android:exported="true"
+ android:visibleToInstantApps="true">
<intent-filter>
<action android:name="android.sensorprivacy.cts.usemiccamera.action.USE_MIC_CAM" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
index b053abb..53de283 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
@@ -24,14 +24,22 @@
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.set;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
+import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchSpec;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
+import android.content.pm.Signature;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArraySet;
import com.android.compatibility.common.util.CddTest;
@@ -41,6 +49,13 @@
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
/**
* Tests for {@link ShortcutManager} and {@link ShortcutInfo}.
@@ -1413,6 +1428,62 @@
});
}
+ public void testUpdateShortcutVisibility_GrantShortcutAccess() throws Exception {
+ final List<byte[]> certs = new ArrayList<>(1);
+
+ // retrieve cert from package1
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ try {
+ final PackageManager pm = mPackageContext1.getPackageManager();
+ final String pkgName = mPackageContext1.getPackageName();
+ PackageInfo packageInfo = pm.getPackageInfo(pkgName, PackageManager.GET_SIGNATURES);
+ for (Signature signature : packageInfo.signatures) {
+ MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+ certs.add(sha256.digest(signature.toByteArray()));
+ }
+ } catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
+ }
+ });
+
+ // Push shortcuts for package2 and make them visible to package1
+ runWithCallerWithStrictMode(mPackageContext2, () -> {
+ final ShortcutManager manager = getManager();
+ for (byte[] cert : certs) {
+ manager.updateShortcutVisibility(mPackageContext1.getPackageName(), cert, true);
+ }
+ assertTrue(manager.setDynamicShortcuts(list(
+ makeShortcut("s1", "1a"),
+ makeShortcut("s2", "2a"),
+ makeShortcut("s3", "3a"))));
+ });
+
+ // Verify package1 can see these shortcuts
+ final Executor executor = Executors.newSingleThreadExecutor();
+ runWithCallerWithStrictMode(mPackageContext1, () -> {
+ final AppSearchManager apm = mPackageContext1.getSystemService(
+ AppSearchManager.class);
+ apm.createGlobalSearchSession(executor, res -> {
+ assertTrue(res.getErrorMessage(), res.isSuccess());
+ res.getResultValue().search("", new SearchSpec.Builder()
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build()
+ ).getNextPage(executor, page -> {
+ assertTrue(page.getErrorMessage(), page.isSuccess());
+ final List<SearchResult> results = page.getResultValue();
+ final Set<String> shortcuts =
+ new ArraySet<>(results.size());
+ for (SearchResult result : results) {
+ shortcuts.add(result.getDocument().getUri());
+ }
+ final Set<String> expected = new ArraySet<>(3);
+ expected.add("s1");
+ expected.add("s2");
+ expected.add("s3");
+ assertEquals("Unexpected results", expected, shortcuts);
+ });
+ });
+ });
+ }
+
public void testDisableAndEnableShortcut() {
runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().setDynamicShortcuts(list(
@@ -2050,7 +2121,7 @@
.forShortcutWithId("s4x", si -> assertEquals(2, si.getRank()));
});
- // Push when limit is reached.
+ // Push more shortcuts.
runWithCallerWithStrictMode(mPackageContext1, () -> {
assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("s", 1, 15))));
assertWith(getManager().getDynamicShortcuts())
@@ -2074,7 +2145,8 @@
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
- .haveIds(makeIds("s", 2, 16))
+ .haveIds(makeIds("s", 1, 16))
+ .forShortcutWithId("s1", si -> assertEquals(15, si.getRank()))
.forShortcutWithId("s2", si -> assertEquals(1, si.getRank()))
.forShortcutWithId("s15", si -> assertEquals(14, si.getRank()))
.forShortcutWithId("s16", si -> assertEquals(0, si.getRank()));
@@ -2084,10 +2156,11 @@
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
- .haveIds(makeIds("s", 3, 17))
+ .haveIds(makeIds("s", 1, 17))
+ .forShortcutWithId("s2", si -> assertEquals(14, si.getRank()))
.forShortcutWithId("s3", si -> assertEquals(1, si.getRank()))
.forShortcutWithId("s16", si -> assertEquals(0, si.getRank()))
- .forShortcutWithId("s17", si -> assertEquals(14, si.getRank()));
+ .forShortcutWithId("s17", si -> assertEquals(16, si.getRank()));
});
setDefaultLauncher(getInstrumentation(), mLauncherContext1);
@@ -2103,10 +2176,10 @@
assertWith(getManager().getDynamicShortcuts())
.areAllEnabled()
.areAllDynamic()
- .haveIds(makeIds("s", 4, 18))
+ .haveIds(makeIds("s", 1, 18))
.forShortcutWithId("s4", si -> assertEquals(2, si.getRank()))
.forShortcutWithId("s16", si -> assertEquals(1, si.getRank()))
- .forShortcutWithId("s17", si -> assertEquals(14, si.getRank()))
+ .forShortcutWithId("s17", si -> assertEquals(17, si.getRank()))
.forShortcutWithId("s18", si -> assertEquals(0, si.getRank()));
assertWith(getManager().getPinnedShortcuts())
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
deleted file mode 100644
index c19b9bc..0000000
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMaxCountTest.java
+++ /dev/null
@@ -1,250 +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.content.pm.cts.shortcutmanager;
-
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicShortcutCountExceeded;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
-
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.compatibility.common.util.CddTest;
-
-@CddTest(requirement="3.8.1/C-4-1")
-@SmallTest
-public class ShortcutManagerMaxCountTest extends ShortcutManagerCtsTestsBase {
- /**
- * Basic tests: single app, single activity, no manifest shortcuts.
- */
- public void testNumDynamicShortcuts() {
- runWithCallerWithStrictMode(mPackageContext1, () -> {
- assertTrue(getManager().setDynamicShortcuts(list(makeShortcut("s1"))));
-
- assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("s", 1, 15))));
- assertWith(getManager().getDynamicShortcuts())
- .haveIds(makeIds("s", 1, 15))
- .areAllDynamic()
- .areAllEnabled();
-
- assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("sx", 1, 15))));
-
- assertDynamicShortcutCountExceeded(() -> {
- getManager().setDynamicShortcuts(makeShortcuts(makeIds("sy", 1, 16)));
- });
-
- assertWith(getManager().getDynamicShortcuts())
- .haveIds(makeIds("sx", 1, 15))
- .areAllDynamic()
- .areAllEnabled();
-
- assertDynamicShortcutCountExceeded(() -> {
- getManager().addDynamicShortcuts(list(
- makeShortcut("sy1")));
- });
- assertWith(getManager().getDynamicShortcuts())
- .haveIds(makeIds("sx", 1, 15))
- .areAllDynamic()
- .areAllEnabled();
- getManager().removeDynamicShortcuts(list("sx15"));
- assertTrue(getManager().addDynamicShortcuts(list(
- makeShortcut("sy1"))));
-
- assertWith(getManager().getDynamicShortcuts())
- .haveIds("sx1", "sx2", "sx3", "sx4", "sx5", "sx6", "sx7", "sx8", "sx9", "sx10",
- "sx11", "sx12", "sx13", "sx14", "sy1")
- .areAllDynamic()
- .areAllEnabled();
-
- getManager().removeAllDynamicShortcuts();
-
- assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("s", 1, 15))));
- assertWith(getManager().getDynamicShortcuts())
- .haveIds(makeIds("s", 1, 15))
- .areAllDynamic()
- .areAllEnabled();
- });
- }
-
- /**
- * Manifest shortcuts are included in the count too.
- */
- public void testWithManifest() throws Exception {
- runWithCallerWithStrictMode(mPackageContext1, () -> {
- enableManifestActivity("Launcher_manifest_1", true);
- enableManifestActivity("Launcher_manifest_2", true);
-
- retryUntil(() -> getManager().getManifestShortcuts().size() == 3,
- "Manifest shortcuts didn't show up");
-
- });
-
- runWithCallerWithStrictMode(mPackageContext1, () -> {
- assertWith(getManager().getManifestShortcuts())
- .haveIds("ms1", "ms21", "ms22")
- .areAllManifest()
- .areAllEnabled()
- .areAllNotPinned()
-
- .selectByIds("ms1")
- .forAllShortcuts(sa -> {
- assertEquals(getActivity("Launcher_manifest_1"), sa.getActivity());
- })
-
- .revertToOriginalList()
- .selectByIds("ms21", "ms22")
- .forAllShortcuts(sa -> {
- assertEquals(getActivity("Launcher_manifest_2"), sa.getActivity());
- });
-
- });
-
- // Note since max counts is per activity, testNumDynamicShortcuts_single should just pass.
- testNumDynamicShortcuts();
-
- // Launcher_manifest_1 has one manifest, so can only add 14 dynamic shortcuts.
- runWithCallerWithStrictMode(mPackageContext1, () -> {
- setTargetActivityOverride("Launcher_manifest_1");
-
- assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("s", 1, 14))));
- assertWith(getManager().getDynamicShortcuts())
- .selectByActivity(getActivity("Launcher_manifest_1"))
- .haveIds(makeIds("s", 1, 14))
- .areAllEnabled();
-
- assertDynamicShortcutCountExceeded(() -> getManager().setDynamicShortcuts(
- makeShortcuts(makeIds("sx", 1, 15))));
- // Not changed.
- assertWith(getManager().getDynamicShortcuts())
- .selectByActivity(getActivity("Launcher_manifest_1"))
- .haveIds(makeIds("s", 1, 14))
- .areAllEnabled();
- });
-
- // Launcher_manifest_2 has two manifests, so can only add 13.
- runWithCallerWithStrictMode(mPackageContext1, () -> {
- setTargetActivityOverride("Launcher_manifest_2");
-
- assertTrue(getManager().addDynamicShortcuts(makeShortcuts(makeIds("s", 1, 13))));
- assertWith(getManager().getDynamicShortcuts())
- .selectByActivity(getActivity("Launcher_manifest_2"))
- .haveIds(makeIds("s", 1, 13))
- .areAllEnabled();
-
- assertDynamicShortcutCountExceeded(() -> getManager().addDynamicShortcuts(list(
- makeShortcut("sx1")
- )));
- // Not added.
- assertWith(getManager().getDynamicShortcuts())
- .selectByActivity(getActivity("Launcher_manifest_2"))
- .haveIds(makeIds("s", 1, 13))
- .areAllEnabled();
- });
- }
-
- public void testChangeActivity() {
- runWithCallerWithStrictMode(mPackageContext1, () -> {
- setTargetActivityOverride("Launcher");
- assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("s", 1, 15))));
- assertWith(getManager().getDynamicShortcuts())
- .selectByActivity(getActivity("Launcher"))
- .haveIds(makeIds("s", 1, 15))
- .areAllDynamic()
- .areAllEnabled();
-
- setTargetActivityOverride("Launcher2");
- assertTrue(getManager().addDynamicShortcuts(makeShortcuts(makeIds("sb", 1, 15))));
-
- assertWith(getManager().getDynamicShortcuts())
- .selectByActivity(getActivity("Launcher"))
- .haveIds(makeIds("s", 1, 15))
- .areAllDynamic()
- .areAllEnabled()
-
- .revertToOriginalList()
- .selectByActivity(getActivity("Launcher2"))
- .haveIds(makeIds("sb", 1, 15))
- .areAllDynamic()
- .areAllEnabled();
-
- // Moving one from L1 to L2 is not allowed.
- assertDynamicShortcutCountExceeded(() -> getManager().updateShortcuts(list(
- makeShortcut("s1", getActivity("Launcher2"))
- )));
-
- assertWith(getManager().getDynamicShortcuts())
- .selectByActivity(getActivity("Launcher"))
- .haveIds(makeIds("s", 1, 15))
- .areAllDynamic()
- .areAllEnabled()
-
- .revertToOriginalList()
- .selectByActivity(getActivity("Launcher2"))
- .haveIds(makeIds("sb", 1, 15))
- .areAllDynamic()
- .areAllEnabled();
-
- // But swapping shortcuts will work.
- assertTrue(getManager().updateShortcuts(list(
- makeShortcut("s1", getActivity("Launcher2")),
- makeShortcut("sb1", getActivity("Launcher"))
- )));
-
- assertWith(getManager().getDynamicShortcuts())
- .selectByActivity(getActivity("Launcher"))
- .haveIds("sb1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11",
- "s12", "s13", "s14", "s15")
- .areAllDynamic()
- .areAllEnabled()
-
- .revertToOriginalList()
- .selectByActivity(getActivity("Launcher2"))
- .haveIds("s1", "sb2", "sb3", "sb4", "sb5", "sb6", "sb7", "sb8", "sb9", "sb10",
- "sb11", "sb12", "sb13", "sb14", "sb15")
- .areAllDynamic()
- .areAllEnabled();
- });
- }
-
- public void testWithPinned() {
- runWithCallerWithStrictMode(mPackageContext1, () -> {
- assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("s", 1, 15))));
- });
-
- setDefaultLauncher(getInstrumentation(), mLauncherContext1);
-
- runWithCallerWithStrictMode(mLauncherContext1, () -> {
- getLauncherApps().pinShortcuts(mPackageContext1.getPackageName(),
- list(makeIds("s", 1, 15)),
- getUserHandle());
- });
-
- runWithCallerWithStrictMode(mPackageContext1, () -> {
- assertTrue(getManager().setDynamicShortcuts(makeShortcuts(makeIds("sb", 1, 15))));
-
- assertWith(getManager().getDynamicShortcuts())
- .haveIds(makeIds("sb", 1, 15))
- .areAllEnabled()
- .areAllNotPinned();
-
- assertWith(getManager().getPinnedShortcuts())
- .haveIds(makeIds("s", 1, 15))
- .areAllEnabled()
- .areAllNotDynamic();
- });
- }
-}
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java
index 1d7e8fc..3736f79 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerMiscTest.java
@@ -34,8 +34,6 @@
public void testMiscApis() throws Exception {
ShortcutManager manager = getTestContext().getSystemService(ShortcutManager.class);
- assertEquals(15, manager.getMaxShortcutCountPerActivity());
-
// during the test, this process always considered to be in the foreground.
assertFalse(manager.isRateLimitingActive());
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index 31f9a5e..cfc71a2 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -25,6 +25,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
+import android.app.AppOpsManager;
import android.app.UiModeManager;
import android.content.Context;
import android.content.Intent;
@@ -38,6 +39,7 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.Process;
import android.provider.CallLog;
import android.telecom.Call;
import android.telecom.CallAudioState;
@@ -119,9 +121,9 @@
MockConnectionService connectionService = null;
boolean mIsEmergencyCallingSetup = false;
- HandlerThread mPhoneStateListenerThread;
- Handler mPhoneStateListenerHandler;
- TestPhoneStateListener mPhoneStateListener;
+ HandlerThread mTelephonyCallbackThread;
+ Handler mTelephonyCallbackHandler;
+ TestTelephonyCallback mTelephonyCallback;
TestCallStateListener mTestCallStateListener;
Handler mHandler;
@@ -205,20 +207,23 @@
}
}
- static class TestPhoneStateListener extends PhoneStateListener {
+ static class TestTelephonyCallback extends TelephonyCallback implements
+ TelephonyCallback.CallStateListener,
+ TelephonyCallback.OutgoingEmergencyCallListener,
+ TelephonyCallback.EmergencyNumberListListener {
/** Semaphore released for every callback invocation. */
public Semaphore mCallbackSemaphore = new Semaphore(0);
- List<Pair<Integer, String>> mCallStates = new ArrayList<>();
+ List<Integer> mCallStates = new ArrayList<>();
EmergencyNumber mLastOutgoingEmergencyNumber;
LinkedBlockingQueue<Map<Integer, List<EmergencyNumber>>> mEmergencyNumberListQueue =
new LinkedBlockingQueue<>(2);
@Override
- public void onCallStateChanged(int state, String number) {
- Log.i(TAG, "onCallStateChanged: state=" + state + ", number=" + number);
- mCallStates.add(Pair.create(state, number));
+ public void onCallStateChanged(int state) {
+ Log.i(TAG, "onCallStateChanged: state=" + state);
+ mCallStates.add(state);
mCallbackSemaphore.release();
}
@@ -262,6 +267,11 @@
TestUtils.executeShellCommand(getInstrumentation(), "telecom reset-car-mode");
assertUiMode(Configuration.UI_MODE_TYPE_NORMAL);
+ AppOpsManager aom = mContext.getSystemService(AppOpsManager.class);
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(aom,
+ (appOpsMan) -> appOpsMan.setUidMode(AppOpsManager.OPSTR_PROCESS_OUTGOING_CALLS,
+ Process.myUid(), AppOpsManager.MODE_ALLOWED));
+
mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
@@ -269,33 +279,21 @@
TestUtils.setDefaultDialer(getInstrumentation(), PACKAGE);
setupCallbacks();
- // PhoneStateListener's public API registers the listener on the calling thread, which must
- // be a looper thread. So we need to create and register the listener in a custom looper
- // thread.
- mPhoneStateListenerThread = new HandlerThread("PhoneStateListenerThread");
- mPhoneStateListenerThread.start();
- mPhoneStateListenerHandler = new Handler(mPhoneStateListenerThread.getLooper());
- final CountDownLatch registeredLatch = new CountDownLatch(1);
- mPhoneStateListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mPhoneStateListener = new TestPhoneStateListener();
- ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
- (tm) -> tm.listen(mPhoneStateListener,
- PhoneStateListener.LISTEN_CALL_STATE
- | PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL
- | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST));
- registeredLatch.countDown();
- }
- });
- registeredLatch.await(
- TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS);
-
- // Register a call state listener.
+ // Register a call state listener.
mTestCallStateListener = new TestCallStateListener();
mTelephonyManager.registerTelephonyCallback(r -> r.run(), mTestCallStateListener);
mTestCallStateListener.getCountDownLatch().await(
TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS);
+ // Create a new thread for the telephony callback.
+ mTelephonyCallbackThread = new HandlerThread("PhoneStateListenerThread");
+ mTelephonyCallbackThread.start();
+ mTelephonyCallbackHandler = new Handler(mTelephonyCallbackThread.getLooper());
+
+ mTelephonyCallback = new TestTelephonyCallback();
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+ (tm) -> tm.registerTelephonyCallback(
+ mTelephonyCallbackHandler::post,
+ mTelephonyCallback));
}
@Override
@@ -307,17 +305,8 @@
mTelephonyManager.unregisterTelephonyCallback(mTestCallStateListener);
- final CountDownLatch unregisteredLatch = new CountDownLatch(1);
- mPhoneStateListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
- unregisteredLatch.countDown();
- }
- });
- unregisteredLatch.await(
- TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_REGISTERED_TIMEOUT_S, TimeUnit.SECONDS);
- mPhoneStateListenerThread.quit();
+ mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback);
+ mTelephonyCallbackThread.quit();
cleanupCalls();
if (!TextUtils.isEmpty(mPreviousDefaultDialer)) {
@@ -962,7 +951,7 @@
void verifyPhoneStateListenerCallbacksForCall(int expectedCallState, String expectedNumber)
throws Exception {
- assertTrue(mPhoneStateListener.mCallbackSemaphore.tryAcquire(
+ assertTrue(mTelephonyCallback.mCallbackSemaphore.tryAcquire(
TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S, TimeUnit.SECONDS));
// At this point we can only be sure that we got AN update, but not necessarily the one we
// are looking for; wait until we see the state we want before verifying further.
@@ -974,12 +963,9 @@
@Override
public Object actual() {
- return mPhoneStateListener.mCallStates
+ return mTelephonyCallback.mCallStates
.stream()
- .filter(p -> p.first.equals(
- expectedCallState)
- && p.second.equals(
- expectedNumber))
+ .filter(p -> p == expectedCallState)
.count() > 0;
}
},
@@ -991,9 +977,9 @@
// Get the most recent callback; it is possible that there was an initial state reported due
// to the fact that TelephonyManager will sometimes give an initial state back to the caller
// when the listener is registered.
- Pair<Integer, String> callState = mPhoneStateListener.mCallStates.get(
- mPhoneStateListener.mCallStates.size() - 1);
- assertEquals(expectedCallState, (int) callState.first);
+ int callState = mTelephonyCallback.mCallStates.get(
+ mTelephonyCallback.mCallStates.size() - 1);
+ assertEquals(expectedCallState, callState);
// Note: We do NOT check the phone number here. Due to changes in how the phone state
// broadcast is sent, the caller may receive multiple broadcasts, and the number will be
// present in one or the other. We waited for a full matching broadcast above so we can
@@ -1002,7 +988,7 @@
void verifyPhoneStateListenerCallbacksForEmergencyCall(String expectedNumber)
throws Exception {
- assertTrue(mPhoneStateListener.mCallbackSemaphore.tryAcquire(
+ assertTrue(mTelephonyCallback.mCallbackSemaphore.tryAcquire(
TestUtils.WAIT_FOR_PHONE_STATE_LISTENER_CALLBACK_TIMEOUT_S, TimeUnit.SECONDS));
// At this point we can only be sure that we got AN update, but not necessarily the one we
// are looking for; wait until we see the state we want before verifying further.
@@ -1014,9 +1000,9 @@
@Override
public Object actual() {
- return mPhoneStateListener
+ return mTelephonyCallback
.mLastOutgoingEmergencyNumber != null
- && mPhoneStateListener
+ && mTelephonyCallback
.mLastOutgoingEmergencyNumber.getNumber()
.equals(expectedNumber);
}
@@ -1024,7 +1010,7 @@
WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
"Expected emergency number: " + expectedNumber);
- assertEquals(mPhoneStateListener.mLastOutgoingEmergencyNumber.getNumber(),
+ assertEquals(mTelephonyCallback.mLastOutgoingEmergencyNumber.getNumber(),
expectedNumber);
}
@@ -1353,16 +1339,17 @@
new Condition() {
@Override
public Object expected() {
- return state;
+ return true;
}
@Override
public Object actual() {
- return call.getState();
+ return call.getState() == state && call.getDetails().getState() == state;
}
},
WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
- "Call: " + call + " should be in state " + state
+ "Expected state: " + state + ", callState=" + call.getState() + ", detailState="
+ + call.getDetails().getState()
);
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDiagnosticServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDiagnosticServiceTest.java
index f03699c..5e2a99c 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDiagnosticServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDiagnosticServiceTest.java
@@ -26,10 +26,11 @@
import android.telecom.BluetoothCallQualityReport;
import android.telecom.Call;
import android.telecom.CallAudioState;
+import android.telecom.CallDiagnostics;
import android.telecom.Connection;
-import android.telecom.DiagnosticCall;
import android.telecom.DisconnectCause;
import android.telecom.TelecomManager;
+import android.telephony.CallQuality;
import android.telephony.TelephonyManager;
import java.util.concurrent.TimeUnit;
@@ -63,9 +64,12 @@
@Override
protected void tearDown() throws Exception {
- super.tearDown();
-
+ if (mConnection != null ) {
+ mConnection.onDisconnect();
+ mConnection.destroy();
+ }
TestUtils.setCallDiagnosticService(getInstrumentation(), "default");
+ super.tearDown();
}
/**
@@ -79,7 +83,7 @@
setupCall();
assertEquals(1, mService.getCalls().size());
- final CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall =
+ final CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall =
mService.getCalls().get(0);
// Add an extra to the connection and verify CDS gets it.
@@ -100,8 +104,6 @@
Connection.EXTRA_AUDIO_CODEC) == Connection.AUDIO_CODEC_AMR_WB;
}
}, TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, "Extras propagation");
-
- mConnection.onDisconnect();
}
/**
@@ -132,10 +134,14 @@
// Disconnect the first call.
mConnection.onDisconnect();
mConnection.destroy();
+ mConnection = null;
mService.getCallChangeLatch().await(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
assertEquals(1, mService.getCalls().size());
+
+ connection.onDisconnect();
+ connection.destroy();
}
@@ -164,8 +170,6 @@
mService.getBluetoothCallQualityReportLatch().await(
TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertEquals(report, mService.getBluetoothCallQualityReport());
-
- mConnection.onDisconnect();
}
/**
@@ -198,15 +202,15 @@
Bundle message = new Bundle();
message.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE,
- DiagnosticCall.MESSAGE_CALL_NETWORK_TYPE);
+ CallDiagnostics.MESSAGE_CALL_NETWORK_TYPE);
message.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE,
TelephonyManager.NETWORK_TYPE_LTE);
mConnection.sendConnectionEvent(Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE, message);
- CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall = mService.getCalls().get(0);
+ CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
diagnosticCall.getReceivedMessageLatch().await(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
- assertEquals(DiagnosticCall.MESSAGE_CALL_NETWORK_TYPE,
+ assertEquals(CallDiagnostics.MESSAGE_CALL_NETWORK_TYPE,
diagnosticCall.getMessageType());
assertEquals(TelephonyManager.NETWORK_TYPE_LTE,
diagnosticCall.getMessageValue());
@@ -222,9 +226,9 @@
}
setupCall();
- CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall = mService.getCalls().get(0);
- diagnosticCall.sendDeviceToDeviceMessage(DiagnosticCall.MESSAGE_DEVICE_BATTERY_STATE,
- DiagnosticCall.BATTERY_STATE_LOW);
+ CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
+ diagnosticCall.sendDeviceToDeviceMessage(CallDiagnostics.MESSAGE_DEVICE_BATTERY_STATE,
+ CallDiagnostics.BATTERY_STATE_LOW);
final TestUtils.InvokeCounter counter = mConnection.getInvokeCounter(
MockConnection.ON_CALL_EVENT);
@@ -236,8 +240,8 @@
assertNotNull(extras);
int messageType = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE);
int messageValue = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE);
- assertEquals(DiagnosticCall.MESSAGE_DEVICE_BATTERY_STATE, messageType);
- assertEquals(DiagnosticCall.BATTERY_STATE_LOW, messageValue);
+ assertEquals(CallDiagnostics.MESSAGE_DEVICE_BATTERY_STATE, messageType);
+ assertEquals(CallDiagnostics.BATTERY_STATE_LOW, messageValue);
}
/**
@@ -250,7 +254,7 @@
}
setupCall();
- CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall = mService.getCalls().get(0);
+ CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
diagnosticCall.displayDiagnosticMessage(POOR_MESSAGE_ID, POOR_CALL_MESSAGE);
mOnConnectionEventCounter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
@@ -274,7 +278,7 @@
}
setupCall();
- CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall = mService.getCalls().get(0);
+ CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
diagnosticCall.clearDiagnosticMessage(POOR_MESSAGE_ID);
mOnConnectionEventCounter.waitForCount(1, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
@@ -298,7 +302,7 @@
mService.setDisconnectMessage(null);
mConnection.setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
mConnection.destroy();
- CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall = mService.getCalls().get(0);
+ CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
diagnosticCall.getDisconnectLatch().await(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
@@ -319,7 +323,7 @@
mService.setDisconnectMessage(OVERRIDE_MESSAGE);
mConnection.setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
mConnection.destroy();
- CtsCallDiagnosticService.CtsDiagnosticCall diagnosticCall = mService.getCalls().get(0);
+ CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
diagnosticCall.getDisconnectLatch().await(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
@@ -329,6 +333,39 @@
}
/**
+ * Test call quality report received.
+ * @throws InterruptedException
+ */
+ public void testReceiveCallQualityReport() throws InterruptedException {
+ if (!shouldTestTelecom(mContext)) {
+ return;
+ }
+ setupCall();
+
+ // Fake out a call quality report.
+ android.telephony.CallQuality callQuality = new CallQuality(
+ android.telephony.CallQuality.CALL_QUALITY_EXCELLENT,
+ android.telephony.CallQuality.CALL_QUALITY_EXCELLENT,
+ 60000, // duration
+ 90210, // transmitted
+ 90210, // received
+ 0, // lost
+ 0, // lost
+ 0, // jitter
+ 0, // jitter
+ 10, // round trip
+ 0); // codec
+ Bundle message = new Bundle();
+ message.putParcelable("android.telecom.extra.CALL_QUALITY_REPORT", callQuality);
+ mConnection.sendConnectionEvent("android.telecom.event.CALL_QUALITY_REPORT", message);
+
+ CtsCallDiagnosticService.CtsCallDiagnostics diagnosticCall = mService.getCalls().get(0);
+ diagnosticCall.getCallQualityReceivedLatch().await(
+ TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertNotNull(diagnosticCall.getCallQuality());
+ }
+
+ /**
* Starts a fake SIM call and verifies binding to the CDS.
* @throws InterruptedException
*/
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsCallDiagnosticService.java b/tests/tests/telecom/src/android/telecom/cts/CtsCallDiagnosticService.java
index aaabedf..be07d09 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CtsCallDiagnosticService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsCallDiagnosticService.java
@@ -21,7 +21,7 @@
import android.telecom.Call;
import android.telecom.CallAudioState;
import android.telecom.CallDiagnosticService;
-import android.telecom.DiagnosticCall;
+import android.telecom.CallDiagnostics;
import android.telephony.CallQuality;
import android.telephony.ims.ImsReasonInfo;
import android.util.Log;
@@ -42,10 +42,10 @@
private CountDownLatch mChangeLatch = new CountDownLatch(1);
private CountDownLatch mBluetoothCallQualityReportLatch = new CountDownLatch(1);
private CountDownLatch mCallAudioStateLatch = new CountDownLatch(1);
- private List<CtsDiagnosticCall> mCalls = new ArrayList<>();
+ private List<CtsCallDiagnostics> mCalls = new ArrayList<>();
private CharSequence mDisconnectMessage = null;
- public class CtsDiagnosticCall extends DiagnosticCall {
+ public class CtsCallDiagnostics extends CallDiagnostics {
private Call.Details mCallDetails;
private int mMessageType;
private int mMessageValue;
@@ -143,19 +143,21 @@
@NonNull
@Override
- public DiagnosticCall onInitializeDiagnosticCall(@NonNull Call.Details call) {
- CtsDiagnosticCall diagCall = new CtsDiagnosticCall();
+ public CallDiagnostics onInitializeCallDiagnostics(@NonNull Call.Details call) {
+ CtsCallDiagnostics diagCall = new CtsCallDiagnostics();
diagCall.mCallDetails = call;
mCalls.add(diagCall);
mChangeLatch.countDown();
+ mChangeLatch = new CountDownLatch(1);
return diagCall;
}
@Override
- public void onRemoveDiagnosticCall(@NonNull DiagnosticCall call) {
+ public void onRemoveCallDiagnostics(@NonNull CallDiagnostics call) {
Log.i(LOG_TAG, "onRemoveDiagnosticCall: " + call);
mCalls.remove(call);
mChangeLatch.countDown();
+ mChangeLatch = new CountDownLatch(1);
}
@Override
@@ -192,16 +194,14 @@
}
public CountDownLatch getCallChangeLatch() {
- CountDownLatch latch = mChangeLatch;
- mChangeLatch = new CountDownLatch(1);
- return latch;
+ return mChangeLatch;
}
public CountDownLatch getBluetoothCallQualityReportLatch() {
return mBluetoothCallQualityReportLatch;
}
- public List<CtsDiagnosticCall> getCalls() {
+ public List<CtsCallDiagnostics> getCalls() {
return mCalls;
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
index b54a511..87676b0 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
@@ -62,6 +62,7 @@
@Override
public void onBindClient(Intent intent) {
sTelecomConnectionService = this;
+ Log.i("TelecomCTS", "CS bound");
sIsBound = true;
}
@@ -338,8 +339,8 @@
@Override
public boolean onUnbind(Intent intent) {
Log.i(LOG_TAG, "Service has been unbound");
- sServiceUnBoundLatch.countDown();
sIsBound = false;
+ sServiceUnBoundLatch.countDown();
sConnectionService = null;
sTelecomConnectionService = null;
return super.onUnbind(intent);
diff --git a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
index 920b11f..d2cdd0c 100644
--- a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
@@ -133,7 +133,7 @@
Map<Integer, List<EmergencyNumber>> emergencyNumbers = null;
for (int i = 0; i < 5; i++) {
- emergencyNumbers = mPhoneStateListener.waitForEmergencyNumberListUpdate(
+ emergencyNumbers = mTelephonyCallback.waitForEmergencyNumberListUpdate(
TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
assertNotNull("Never got an update that the test emergency number was registered",
emergencyNumbers);
diff --git a/tests/tests/telephony/current/AndroidManifest.xml b/tests/tests/telephony/current/AndroidManifest.xml
index 0805481..6620967 100644
--- a/tests/tests/telephony/current/AndroidManifest.xml
+++ b/tests/tests/telephony/current/AndroidManifest.xml
@@ -34,6 +34,9 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<uses-permission android:name="android.permission.USE_SIP"/>
<uses-permission android:name="android.telephony.embms.cts.permission.TEST_BROADCAST"/>
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
index ea36271..8229f98 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
@@ -18,8 +18,8 @@
import static android.telephony.data.DataCallResponse.HANDOVER_FAILURE_MODE_DO_FALLBACK;
import static android.telephony.data.DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY;
-import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_EMBB;
-import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_MIOT;
+import static android.telephony.data.NetworkSliceInfo.SLICE_SERVICE_TYPE_EMBB;
+import static android.telephony.data.NetworkSliceInfo.SLICE_SERVICE_TYPE_MIOT;
import static com.google.common.truth.Truth.assertThat;
@@ -28,7 +28,7 @@
import android.os.Parcel;
import android.telephony.data.ApnSetting;
import android.telephony.data.DataCallResponse;
-import android.telephony.data.SliceInfo;
+import android.telephony.data.NetworkSliceInfo;
import android.telephony.data.TrafficDescriptor;
import org.junit.Test;
@@ -60,8 +60,8 @@
private static final int TEST_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_EMBB;
private static final int TEST_HPLMN_SLICE_DIFFERENTIATOR = 10;
private static final int TEST_HPLMN_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_MIOT;
- private static final SliceInfo SLICE_INFO =
- new SliceInfo.Builder()
+ private static final NetworkSliceInfo SLICE_INFO =
+ new NetworkSliceInfo.Builder()
.setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
.setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
.setMappedHplmnSliceDifferentiator(TEST_HPLMN_SLICE_DIFFERENTIATOR)
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/LteVopsSupportInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/LteVopsSupportInfoTest.java
deleted file mode 100644
index 1832fad..0000000
--- a/tests/tests/telephony/current/src/android/telephony/cts/LteVopsSupportInfoTest.java
+++ /dev/null
@@ -1,44 +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.telephony.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Parcel;
-import android.telephony.LteVopsSupportInfo;
-
-import org.junit.Test;
-
-public class LteVopsSupportInfoTest {
-
- @Test
- public void testLteVopsSupportInfo() {
- LteVopsSupportInfo lteVops =
- new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
- LteVopsSupportInfo.LTE_STATUS_SUPPORTED);
- assertEquals(0, lteVops.describeContents());
- assertEquals(LteVopsSupportInfo.LTE_STATUS_SUPPORTED, lteVops.getVopsSupport());
- assertEquals(LteVopsSupportInfo.LTE_STATUS_SUPPORTED, lteVops.getEmcBearerSupport());
-
- Parcel lteVopsParcel = Parcel.obtain();
- lteVops.writeToParcel(lteVopsParcel, 0);
- lteVopsParcel.setDataPosition(0);
- LteVopsSupportInfo checkLteVops =
- LteVopsSupportInfo.CREATOR.createFromParcel(lteVopsParcel);
- assertTrue(lteVops.equals(checkLteVops));
- }
-}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SliceInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/NetworkSliceInfoTest.java
similarity index 62%
rename from tests/tests/telephony/current/src/android/telephony/cts/SliceInfoTest.java
rename to tests/tests/telephony/current/src/android/telephony/cts/NetworkSliceInfoTest.java
index d0ab1ae..8e2d490 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SliceInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/NetworkSliceInfoTest.java
@@ -16,19 +16,19 @@
package android.telephony.cts;
-import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_EMBB;
-import static android.telephony.data.SliceInfo.SLICE_SERVICE_TYPE_MIOT;
+import static android.telephony.data.NetworkSliceInfo.SLICE_SERVICE_TYPE_EMBB;
+import static android.telephony.data.NetworkSliceInfo.SLICE_SERVICE_TYPE_MIOT;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.os.Parcel;
-import android.telephony.data.SliceInfo;
+import android.telephony.data.NetworkSliceInfo;
import org.junit.Test;
-public class SliceInfoTest {
+public class NetworkSliceInfoTest {
private static final int TEST_SLICE_DIFFERENTIATOR = 1;
private static final int TEST_SLICE_SERVICE_TYPE = SLICE_SERVICE_TYPE_EMBB;
private static final int TEST_HPLMN_SLICE_DIFFERENTIATOR = 10;
@@ -36,22 +36,22 @@
@Test
public void testParceling() {
- testParceling(new SliceInfo.Builder()
+ testParceling(new NetworkSliceInfo.Builder()
.setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
.build());
- testParceling(new SliceInfo.Builder()
+ testParceling(new NetworkSliceInfo.Builder()
.setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
.setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
.build());
- testParceling(new SliceInfo.Builder()
+ testParceling(new NetworkSliceInfo.Builder()
.setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
.setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
.setMappedHplmnSliceServiceType(TEST_HPLMN_SLICE_SERVICE_TYPE)
.build());
- testParceling(new SliceInfo.Builder()
+ testParceling(new NetworkSliceInfo.Builder()
.setSliceServiceType(TEST_SLICE_SERVICE_TYPE)
.setSliceDifferentiator(TEST_SLICE_DIFFERENTIATOR)
.setMappedHplmnSliceServiceType(TEST_HPLMN_SLICE_SERVICE_TYPE)
@@ -59,47 +59,49 @@
.build());
}
- private void testParceling(SliceInfo sliceInfo1) {
+ private void testParceling(NetworkSliceInfo sliceInfo1) {
Parcel stateParcel = Parcel.obtain();
sliceInfo1.writeToParcel(stateParcel, 0);
stateParcel.setDataPosition(0);
- SliceInfo parcelResponse = SliceInfo.CREATOR.createFromParcel(stateParcel);
+ NetworkSliceInfo parcelResponse = NetworkSliceInfo.CREATOR.createFromParcel(stateParcel);
assertThat(parcelResponse).isEqualTo(sliceInfo1);
}
@Test
public void testSliceDifferentiatorRange() {
- new SliceInfo.Builder()
- .setSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR)
- .setSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR)
- .setMappedHplmnSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR)
- .setMappedHplmnSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR);
+ new NetworkSliceInfo.Builder()
+ .setSliceDifferentiator(NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR)
+ .setSliceDifferentiator(NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR)
+ .setMappedHplmnSliceDifferentiator(NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR)
+ .setMappedHplmnSliceDifferentiator(NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR);
try {
- new SliceInfo.Builder()
- .setSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR - 1);
+ new NetworkSliceInfo.Builder()
+ .setSliceDifferentiator(NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR - 1);
fail("Illegal state exception expected");
} catch (IllegalArgumentException ignored) {
}
try {
- new SliceInfo.Builder()
- .setMappedHplmnSliceDifferentiator(SliceInfo.MIN_SLICE_DIFFERENTIATOR - 1);
+ new NetworkSliceInfo.Builder()
+ .setMappedHplmnSliceDifferentiator(
+ NetworkSliceInfo.MIN_SLICE_DIFFERENTIATOR - 1);
fail("Illegal state exception expected");
} catch (IllegalArgumentException ignored) {
}
try {
- new SliceInfo.Builder()
- .setSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR + 1);
+ new NetworkSliceInfo.Builder()
+ .setSliceDifferentiator(NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR + 1);
fail("Illegal state exception expected");
} catch (IllegalArgumentException ignored) {
}
try {
- new SliceInfo.Builder()
- .setMappedHplmnSliceDifferentiator(SliceInfo.MAX_SLICE_DIFFERENTIATOR + 1);
+ new NetworkSliceInfo.Builder()
+ .setMappedHplmnSliceDifferentiator(
+ NetworkSliceInfo.MAX_SLICE_DIFFERENTIATOR + 1);
fail("Illegal state exception expected");
} catch (IllegalArgumentException ignored) {
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhoneCapabilityTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhoneCapabilityTest.java
new file mode 100644
index 0000000..3ed8a69
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhoneCapabilityTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.telephony.ModemInfo;
+import android.telephony.PhoneCapability;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PhoneCapabilityTest {
+ @Test
+ @SmallTest
+ public void parcelReadWrite() throws Exception {
+ int maxActiveVoice = 1;
+ int maxActiveData = 2;
+ ModemInfo modemInfo = new ModemInfo(1, 2, true, false);
+ List<ModemInfo> logicalModemList = new ArrayList<>();
+ logicalModemList.add(modemInfo);
+ int[] deviceNrCapabilities = new int[]{};
+
+ Parcel parcel = Parcel.obtain();
+ parcel.writeInt(maxActiveVoice);
+ parcel.writeInt(maxActiveData);
+ parcel.writeBoolean(false);
+ parcel.writeList(logicalModemList);
+ parcel.writeIntArray(deviceNrCapabilities);
+
+ parcel.setDataPosition(0);
+ PhoneCapability toCompare = PhoneCapability.CREATOR.createFromParcel(parcel);
+
+ assertEquals(maxActiveVoice, toCompare.getMaxActiveVoiceSubscriptions());
+ assertEquals(maxActiveData, toCompare.getMaxActiveDataSubscriptions());
+ assertArrayEquals(deviceNrCapabilities, toCompare.getDeviceNrCapabilities());
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
index cc534f7..eaa6b7d 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
@@ -36,6 +36,8 @@
private static final int CONNECTION_STATUS = PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING;
private static final int CELL_BANDWIDTH = 12345;
private static final int CHANNEL_NUMBER = 1234;
+ private static final int DOWNLINK_FREQUENCY = 11100;
+ private static final int UPLINK_FREQUENCY = 11100;
private static final int FREQUENCY_RANGE = 1;
private static final int PHYSICAL_CELL_ID = 502;
private static final int PHYSICAL_INVALID_CELL_ID = 1008;
@@ -168,4 +170,32 @@
assertThat(mPhysicalChannelConfig.getFrequencyRange()).isEqualTo(
ServiceState.FREQUENCY_RANGE_LOW);
}
+
+ private void setupNrPhysicalChannelConfig() {
+ mPhysicalChannelConfig = new PhysicalChannelConfig.Builder()
+ .setPhysicalCellId(PHYSICAL_CELL_ID)
+ .setNetworkType(NETWORK_TYPE_NR)
+ .setCellConnectionStatus(CONNECTION_STATUS)
+ .setCellBandwidthDownlinkKhz(CELL_BANDWIDTH)
+ .setCellBandwidthUplinkKhz(CELL_BANDWIDTH)
+ .setContextIds(CONTEXT_IDS)
+ .setDownlinkChannelNumber(2220)
+ .setUplinkChannelNumber(2220)
+ .setBand(BAND)
+ .build();
+ }
+
+ @Test
+ public void testUplinkFrequencyKhz() {
+ setupNrPhysicalChannelConfig();
+
+ assertEquals(UPLINK_FREQUENCY, mPhysicalChannelConfig.getUplinkFrequencyKhz());
+ }
+
+ @Test
+ public void testDownlinkFrequencyKhz() {
+ setupNrPhysicalChannelConfig();
+
+ assertEquals(DOWNLINK_FREQUENCY, mPhysicalChannelConfig.getDownlinkFrequencyKhz());
+ }
}
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 cb9d1ad..d3ecc7e 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
@@ -56,6 +56,7 @@
import android.os.SystemClock;
import android.provider.Telephony;
import android.telephony.SmsCbMessage;
+import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaSmsCbProgramData;
@@ -801,6 +802,15 @@
}
}
+ @Test
+ public void testCreateForSubscriptionId() {
+ int testSubId = 123;
+ SmsManager smsManager = mContext.getSystemService(SmsManager.class)
+ .createForSubscriptionId(testSubId);
+ assertEquals("getSubscriptionId() should be " + testSubId, testSubId,
+ smsManager.getSubscriptionId());
+ }
+
protected ArrayList<String> divideMessage(String text) {
return getSmsManager().divideMessage(text);
}
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 064f45e..4a550fe 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -40,6 +40,7 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.Uri;
import android.os.Looper;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
@@ -89,6 +90,11 @@
private static final String TAG = "SubscriptionManagerTest";
private static final String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
private SubscriptionManager mSm;
+ private static final List<Uri> CONTACTS = new ArrayList<>();
+ static {
+ CONTACTS.add(Uri.fromParts("tel", "+16505551212", null));
+ CONTACTS.add(Uri.fromParts("tel", "+16505552323", null));
+ }
private int mSubId;
private String mPackageName;
@@ -842,6 +848,9 @@
int activeDataSubId = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
(sm) -> sm.getActiveDataSubscriptionId());
assertNotEquals(activeDataSubId, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ SubscriptionInfo activeSubInfo = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
+ (sm) -> sm.getActiveSubscriptionInfo(activeDataSubId));
+ String isoCountryCode = activeSubInfo.getCountryIso();
byte[] backupData = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
(sm) -> sm.getAllSimSpecificSettingsForBackup());
@@ -860,6 +869,13 @@
mMmTelManager, (m) -> m.isAdvancedCallingSettingEnabled());
boolean isVtImsEnabledOriginal = ShellIdentityUtils.invokeMethodWithShellPermissions(
mMmTelManager, (m) -> m.isVtSettingEnabled());
+ boolean isVoWiFiSettingEnabledOriginal =
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mMmTelManager, (m) -> m.isVoWiFiSettingEnabled());
+ int voWifiModeOriginal = ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mMmTelManager, (m) -> m.getVoWiFiModeSetting());
+ int voWiFiRoamingModeOriginal = ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mMmTelManager, (m) -> m.getVoWiFiRoamingModeSetting());
// Get the original RcsUce values.
ImsRcsManager imsRcsManager = imsManager.getImsRcsManager(activeDataSubId);
@@ -877,16 +893,29 @@
ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
rcsUceAdapter, (a) -> a.setUceSettingEnabled(!isImsRcsUceEnabledOriginal),
ImsException.class);
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mMmTelManager,
+ (m) -> m.setVoWiFiSettingEnabled(!isVoWiFiSettingEnabledOriginal));
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mMmTelManager,
+ (m) -> m.setVoWiFiModeSetting((voWifiModeOriginal + 1) % 3));
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mMmTelManager,
+ (m) -> m.setVoWiFiRoamingModeSetting((voWiFiRoamingModeOriginal + 1) % 3));
// Restore back to original values.
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
(sm) -> sm.restoreAllSimSpecificSettingsFromBackup(backupData));
+
// Get ims values to verify with.
boolean isVolteVtEnabledAfterRestore = ShellIdentityUtils.invokeMethodWithShellPermissions(
mMmTelManager, (m) -> m.isAdvancedCallingSettingEnabled());
boolean isVtImsEnabledAfterRestore = ShellIdentityUtils.invokeMethodWithShellPermissions(
mMmTelManager, (m) -> m.isVtSettingEnabled());
-
+ boolean isVoWiFiSettingEnabledAfterRestore =
+ ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mMmTelManager, (m) -> m.isVoWiFiSettingEnabled());
+ int voWifiModeAfterRestore = ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mMmTelManager, (m) -> m.getVoWiFiModeSetting());
+ int voWiFiRoamingModeAfterRestore = ShellIdentityUtils.invokeMethodWithShellPermissions(
+ mMmTelManager, (m) -> m.getVoWiFiRoamingModeSetting());
// Get RcsUce values to verify with.
boolean isImsRcsUceEnabledAfterRestore =
ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
@@ -894,6 +923,13 @@
android.Manifest.permission.READ_PHONE_STATE);
assertEquals(isVolteVtEnabledOriginal, isVolteVtEnabledAfterRestore);
+ if (isoCountryCode == null || isoCountryCode.equals("us") || isoCountryCode.equals("ca")) {
+ assertEquals(!isVoWiFiSettingEnabledOriginal, isVoWiFiSettingEnabledAfterRestore);
+ } else {
+ assertEquals(isVoWiFiSettingEnabledOriginal, isVoWiFiSettingEnabledAfterRestore);
+ }
+ assertEquals(voWifiModeOriginal, voWifiModeAfterRestore);
+ assertEquals(voWiFiRoamingModeOriginal, voWiFiRoamingModeAfterRestore);
assertEquals(isVtImsEnabledOriginal, isVtImsEnabledAfterRestore);
assertEquals(isImsRcsUceEnabledOriginal, isImsRcsUceEnabledAfterRestore);
@@ -914,14 +950,26 @@
public void testSetAndGetD2DStatusSharing() {
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
uiAutomation.adoptShellPermissionIdentity(MODIFY_PHONE_STATE);
- int originalD2DStatusSharing = mSm.getDeviceToDeviceStatusSharing(mSubId);
- mSm.setDeviceToDeviceStatusSharing(SubscriptionManager.D2D_SHARING_ALL_CONTACTS, mSubId);
+ int originalD2DStatusSharing = mSm.getDeviceToDeviceStatusSharingPreference(mSubId);
+ mSm.setDeviceToDeviceStatusSharingPreference(SubscriptionManager.D2D_SHARING_ALL_CONTACTS,
+ mSubId);
assertEquals(SubscriptionManager.D2D_SHARING_ALL_CONTACTS,
- mSm.getDeviceToDeviceStatusSharing(mSubId));
- mSm.setDeviceToDeviceStatusSharing(SubscriptionManager.D2D_SHARING_ALL, mSubId);
+ mSm.getDeviceToDeviceStatusSharingPreference(mSubId));
+ mSm.setDeviceToDeviceStatusSharingPreference(SubscriptionManager.D2D_SHARING_ALL, mSubId);
assertEquals(SubscriptionManager.D2D_SHARING_ALL,
- mSm.getDeviceToDeviceStatusSharing(mSubId));
- mSm.setDeviceToDeviceStatusSharing(originalD2DStatusSharing, mSubId);
+ mSm.getDeviceToDeviceStatusSharingPreference(mSubId));
+ mSm.setDeviceToDeviceStatusSharingPreference(originalD2DStatusSharing, mSubId);
+ uiAutomation.dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void testSetAndGetD2DSharingContacts() {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ uiAutomation.adoptShellPermissionIdentity(MODIFY_PHONE_STATE);
+ List<Uri> originalD2DSharingContacts = mSm.getDeviceToDeviceStatusSharingContacts(mSubId);
+ mSm.setDeviceToDeviceStatusSharingContacts(CONTACTS, mSubId);
+ assertEquals(CONTACTS, mSm.getDeviceToDeviceStatusSharingContacts(mSubId));
+ mSm.setDeviceToDeviceStatusSharingContacts(originalD2DSharingContacts, mSubId);
uiAutomation.dropShellPermissionIdentity();
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyCallbackTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyCallbackTest.java
index bcda6e6..18095d7 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyCallbackTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyCallbackTest.java
@@ -38,6 +38,7 @@
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.CellLocation;
+import android.telephony.LinkCapacityEstimate;
import android.telephony.PhysicalChannelConfig;
import android.telephony.PreciseCallState;
import android.telephony.PreciseDataConnectionState;
@@ -94,6 +95,7 @@
private boolean mOnTelephonyDisplayInfoChanged;
private boolean mOnPhysicalChannelConfigCalled;
private boolean mOnDataEnabledChangedCalled;
+ private boolean mOnLinkCapacityEstimateChangedCalled;
@RadioPowerState
private int mRadioPowerState;
@SimActivationState
@@ -1378,4 +1380,43 @@
TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER,
originalAllowedNetworkTypeUser));
}
+
+ private LinkCapacityEstimateChangedListener mLinkCapacityEstimateChangedListener;
+
+ private class LinkCapacityEstimateChangedListener extends TelephonyCallback
+ implements TelephonyCallback.LinkCapacityEstimateChangedListener {
+ @Override
+ public void onLinkCapacityEstimateChanged(
+ List<LinkCapacityEstimate> linkCapacityEstimateList) {
+ synchronized (mLock) {
+ mOnLinkCapacityEstimateChangedCalled = true;
+ mLock.notify();
+ }
+ }
+ }
+
+ @Test
+ public void testOnLinkCapacityEstimateChangedByRegisterPhoneStateListener() throws Throwable {
+ if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+ Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+ return;
+ }
+
+ assertFalse(mOnLinkCapacityEstimateChangedCalled);
+ mHandler.post(() -> {
+ mLinkCapacityEstimateChangedListener = new LinkCapacityEstimateChangedListener();
+ registerTelephonyCallbackWithPermission(mLinkCapacityEstimateChangedListener);
+ });
+
+ synchronized (mLock) {
+ while (!mOnLinkCapacityEstimateChangedCalled) {
+ mLock.wait(WAIT_TIME);
+ }
+ }
+ assertTrue(mOnLinkCapacityEstimateChangedCalled);
+
+ // Test unregister
+ unRegisterTelephonyCallback(mOnLinkCapacityEstimateChangedCalled,
+ mLinkCapacityEstimateChangedListener);
+ }
}
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 f746138..0d693b3 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -17,7 +17,6 @@
package android.telephony.cts;
import static android.app.AppOpsManager.OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER;
-import static android.telephony.PhoneCapability.DEVICE_NR_CAPABILITY_NONE;
import static android.telephony.PhoneCapability.DEVICE_NR_CAPABILITY_NSA;
import static android.telephony.PhoneCapability.DEVICE_NR_CAPABILITY_SA;
@@ -25,6 +24,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -62,7 +62,6 @@
import android.telephony.CallAttributes;
import android.telephony.CallForwardingInfo;
import android.telephony.CallQuality;
-import android.telephony.CarrierBandwidth;
import android.telephony.CarrierConfigManager;
import android.telephony.CellIdentity;
import android.telephony.CellIdentityLte;
@@ -90,6 +89,7 @@
import android.telephony.UiccCardInfo;
import android.telephony.UiccSlotInfo;
import android.telephony.data.ApnSetting;
+import android.telephony.data.SlicingConfig;
import android.telephony.emergency.EmergencyNumber;
import android.text.TextUtils;
import android.util.Log;
@@ -202,6 +202,11 @@
private static final int MAX_FPLMN_NUM = 100;
private static final int MIN_FPLMN_NUM = 3;
+ private static final String THERMAL_MITIGATION_COMMAND_BASE = "cmd phone thermal-mitigation ";
+ private static final String ALLOW_PACKAGE_SUBCOMMAND = "allow-package ";
+ private static final String DISALLOW_PACKAGE_SUBCOMMAND = "disallow-package ";
+ private static final String TELEPHONY_CTS_PACKAGE = "android.telephony.cts";
+
private static final String TEST_FORWARD_NUMBER = "54321";
private static final String TESTING_PLMN = "12345";
@@ -356,6 +361,12 @@
if (mIsAllowedNetworkTypeChanged) {
recoverAllowedNetworkType();
}
+
+ StringBuilder cmdBuilder = new StringBuilder();
+ cmdBuilder.append(THERMAL_MITIGATION_COMMAND_BASE).append(DISALLOW_PACKAGE_SUBCOMMAND)
+ .append(TELEPHONY_CTS_PACKAGE);
+ TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+ cmdBuilder.toString());
}
private void saveAllowedNetworkTypesForAllReasons() {
@@ -658,6 +669,7 @@
(tm) -> tm.getSubscriberId());
mTelephonyManager.getLine1Number();
mTelephonyManager.getNetworkOperator();
+ mTelephonyManager.getPhoneAccountHandle();
mTelephonyManager.getSimCountryIso();
mTelephonyManager.getVoiceMailAlphaTag();
mTelephonyManager.isNetworkRoaming();
@@ -970,6 +982,15 @@
assertNull(mTelephonyManager.createForPhoneAccountHandle(handle));
}
+ @Test
+ public void testGetPhoneAccountHandle() {
+ TelecomManager telecomManager = getContext().getSystemService(TelecomManager.class);
+ PhoneAccountHandle defaultAccount = telecomManager
+ .getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
+ PhoneAccountHandle phoneAccountHandle = mTelephonyManager.getPhoneAccountHandle();
+ assertEquals(phoneAccountHandle, defaultAccount);
+ }
+
/**
* Tests that the phone count returned is valid.
*/
@@ -1386,62 +1407,27 @@
}
@Test
- public void testGetPhoneCapability() throws Throwable {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
- return;
- }
-
- // test without permission: verify SecurityException
- try {
- mTelephonyManager.getPhoneCapability();
- fail("testGetPhoneCapability: SecurityException expected");
- } catch (SecurityException se) {
- // expected
- }
-
- assertThat(mOnPhoneCapabilityChanged).isFalse();
- TestThread t = new TestThread(new Runnable() {
- public void run() {
- Looper.prepare();
-
- mMockPhoneCapabilityListener = new MockPhoneCapabilityListener();
- ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
- (tm) -> tm.registerTelephonyCallback(mSimpleExecutor,
- mMockPhoneCapabilityListener));
- Looper.loop();
- }
- });
- synchronized (mLock) {
- t.start();
- mLock.wait(TOLERANCE);
- }
-
- // test with permission
- try {
- PhoneCapability phoneCapability = ShellIdentityUtils.invokeMethodWithShellPermissions(
- mTelephonyManager, (tm) -> tm.getPhoneCapability());
-
- assertEquals(mPhoneCapability, phoneCapability);
- } catch (SecurityException se) {
- fail("testGetPhoneCapability: SecurityException not expected");
- }
- }
-
- @Test
public void testGetPhoneCapabilityAndVerify() {
boolean is5gStandalone = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_telephony5gStandalone);
boolean is5gNonStandalone = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_telephony5gNonStandalone);
- int deviceNrCapability =
- (is5gStandalone ? DEVICE_NR_CAPABILITY_SA : DEVICE_NR_CAPABILITY_NONE) | (
- is5gNonStandalone ? DEVICE_NR_CAPABILITY_NSA : DEVICE_NR_CAPABILITY_NONE);
+ int[] deviceNrCapabilities = new int[0];
+ if (is5gStandalone || is5gNonStandalone) {
+ List<Integer> list = new ArrayList<>();
+ if (is5gNonStandalone) {
+ list.add(DEVICE_NR_CAPABILITY_NSA);
+ }
+ if (is5gStandalone) {
+ list.add(DEVICE_NR_CAPABILITY_SA);
+ }
+ deviceNrCapabilities = list.stream().mapToInt(Integer::valueOf).toArray();
+ }
PhoneCapability phoneCapability = ShellIdentityUtils.invokeMethodWithShellPermissions(
mTelephonyManager, (tm) -> tm.getPhoneCapability());
- assertEquals(deviceNrCapability, phoneCapability.getDeviceNrCapabilityBitmask());
+ assertArrayEquals(deviceNrCapabilities, phoneCapability.getDeviceNrCapabilities());
}
@Test
@@ -3656,19 +3642,6 @@
}
@Test
- public void testGetCarrierBandwidth() {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- return;
- }
- CarrierBandwidth bandwidth =
- ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
- (tm) -> tm.getCarrierBandwidth());
- if (mRadioVersion >= RADIO_HAL_VERSION_1_6) {
- assertTrue(bandwidth != null);
- }
- }
-
- @Test
public void testSetSignalStrengthUpdateRequest_nullRequest() {
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
@@ -3993,10 +3966,17 @@
}
@Test
- public void testSendThermalMitigationRequest() {
+ public void testSendThermalMitigationRequest() throws Exception {
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
return;
}
+
+ StringBuilder cmdBuilder = new StringBuilder();
+ cmdBuilder.append(THERMAL_MITIGATION_COMMAND_BASE).append(ALLOW_PACKAGE_SUBCOMMAND)
+ .append(TELEPHONY_CTS_PACKAGE);
+ TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
+ cmdBuilder.toString());
+
long arbitraryCompletionWindowSecs = 1L;
@@ -4530,5 +4510,19 @@
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
appOpsManager, (appOps) -> appOps.setUidMode(op, Process.myUid(), mode));
}
+
+ /**
+ * Verifies that {@link TelephonyManager#getNetworkSlicingConfiguration()} does not throw any
+ * exception
+ */
+ @Test
+ public void testGetNetworkSlicingConfiguration() {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+ return;
+ }
+ CompletableFuture<SlicingConfig> resultFuture = new CompletableFuture<>();
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+ (tm) -> tm.getNetworkSlicingConfiguration(mSimpleExecutor, resultFuture::complete));
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java
index 5ab27d6..1ab317d 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java
@@ -30,7 +30,7 @@
@Test
public void testConstructorAndGetters() {
TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
- assertThat(td.getDnn()).isEqualTo(DNN);
+ assertThat(td.getDataNetworkName()).isEqualTo(DNN);
assertThat(td.getOsAppId()).isEqualTo(OS_APP_ID);
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/VopsSupportInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/VopsSupportInfoTest.java
new file mode 100644
index 0000000..431da5e
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/VopsSupportInfoTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.telephony.LteVopsSupportInfo;
+import android.telephony.NrVopsSupportInfo;
+import android.telephony.VopsSupportInfo;
+
+import org.junit.Test;
+
+public class VopsSupportInfoTest {
+
+ private static final int[] NR_VOPS_VALUE = {
+ NrVopsSupportInfo.NR_STATUS_VOPS_NOT_SUPPORTED,
+ NrVopsSupportInfo.NR_STATUS_VOPS_3GPP_SUPPORTED,
+ NrVopsSupportInfo.NR_STATUS_VOPS_NON_3GPP_SUPPORTED};
+ private static final int[] NR_EMC_VALUE = {
+ NrVopsSupportInfo.NR_STATUS_EMC_NOT_SUPPORTED,
+ NrVopsSupportInfo.NR_STATUS_EMC_5GCN_ONLY,
+ NrVopsSupportInfo.NR_STATUS_EMC_EUTRA_5GCN_ONLY,
+ NrVopsSupportInfo.NR_STATUS_EMC_NR_EUTRA_5GCN};
+ private static final int[] NR_EMF_VALUE = {
+ NrVopsSupportInfo.NR_STATUS_EMF_NOT_SUPPORTED,
+ NrVopsSupportInfo.NR_STATUS_EMF_5GCN_ONLY,
+ NrVopsSupportInfo.NR_STATUS_EMF_EUTRA_5GCN_ONLY,
+ NrVopsSupportInfo.NR_STATUS_EMF_NR_EUTRA_5GCN};
+
+ @Test
+ public void testLteVopsSupportInfoApi() {
+ VopsSupportInfo vops = new LteVopsSupportInfo(
+ LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+ LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED);
+
+ assertEquals(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+ ((LteVopsSupportInfo) vops).getVopsSupport());
+ assertEquals(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+ ((LteVopsSupportInfo) vops).getEmcBearerSupport());
+ assertFalse(vops.isVopsSupported());
+ assertFalse(vops.isEmergencyServiceSupported());
+ assertFalse(vops.isEmergencyServiceFallbackSupported());
+
+ vops = new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+ LteVopsSupportInfo.LTE_STATUS_SUPPORTED);
+
+ assertEquals(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+ ((LteVopsSupportInfo) vops).getVopsSupport());
+ assertEquals(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+ ((LteVopsSupportInfo) vops).getEmcBearerSupport());
+ assertTrue(vops.isVopsSupported());
+ assertTrue(vops.isEmergencyServiceSupported());
+ assertFalse(vops.isEmergencyServiceFallbackSupported());
+ }
+
+ @Test
+ public void testLteVopsSupportInfoParcel() {
+ LteVopsSupportInfo vops = new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+ LteVopsSupportInfo.LTE_STATUS_SUPPORTED);
+
+ Parcel vopsParcel = Parcel.obtain();
+ vops.writeToParcel(vopsParcel, 0);
+ vopsParcel.setDataPosition(0);
+ LteVopsSupportInfo checkLteVops =
+ LteVopsSupportInfo.CREATOR.createFromParcel(vopsParcel);
+
+ assertEquals(0, vops.describeContents());
+ assertEquals(vops, checkLteVops);
+ }
+
+ @Test
+ public void testNrVopsSupportInfoApi() {
+ for (int i = 0; i < NR_VOPS_VALUE.length; i++) {
+ for (int j = 0; j < NR_EMC_VALUE.length; j++) {
+ for (int k = 0; k < NR_EMF_VALUE.length; k++) {
+ VopsSupportInfo vops = new NrVopsSupportInfo(NR_VOPS_VALUE[i],
+ NR_EMC_VALUE[j], NR_EMF_VALUE[k]);
+
+ assertEquals(NR_VOPS_VALUE[i], ((NrVopsSupportInfo) vops).getVopsSupport());
+ assertEquals(NR_EMC_VALUE[j], ((NrVopsSupportInfo) vops).getEmcSupport());
+ assertEquals(NR_EMF_VALUE[k], ((NrVopsSupportInfo) vops).getEmfSupport());
+ assertEquals(isVopsSupportedByNrVopsInfo(NR_VOPS_VALUE[i]),
+ vops.isVopsSupported());
+ assertEquals(isEmcSupportedByNrVopsInfo(NR_EMC_VALUE[j]),
+ vops.isEmergencyServiceSupported());
+ assertEquals(isEmfSupportedByNrVopsInfo(NR_EMF_VALUE[k]),
+ vops.isEmergencyServiceFallbackSupported());
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testNrVopsSupportInfoParcel() {
+ NrVopsSupportInfo vops = new NrVopsSupportInfo(
+ NrVopsSupportInfo.NR_STATUS_VOPS_3GPP_SUPPORTED,
+ NrVopsSupportInfo.NR_STATUS_EMC_NR_EUTRA_5GCN,
+ NrVopsSupportInfo.NR_STATUS_EMF_NR_EUTRA_5GCN);
+
+ Parcel vopsParcel = Parcel.obtain();
+ vops.writeToParcel(vopsParcel, 0);
+ vopsParcel.setDataPosition(0);
+ NrVopsSupportInfo checkNrVops =
+ NrVopsSupportInfo.CREATOR.createFromParcel(vopsParcel);
+
+ assertEquals(0, vops.describeContents());
+ assertEquals(vops, checkNrVops);
+ }
+
+ private boolean isVopsSupportedByNrVopsInfo(int value) {
+ return value != NrVopsSupportInfo.NR_STATUS_VOPS_NOT_SUPPORTED;
+ }
+
+ private boolean isEmcSupportedByNrVopsInfo(int value) {
+ return value != NrVopsSupportInfo.NR_STATUS_EMC_NOT_SUPPORTED;
+ }
+
+ private boolean isEmfSupportedByNrVopsInfo(int value) {
+ return value != NrVopsSupportInfo.NR_STATUS_EMF_NOT_SUPPORTED;
+ }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
index 9bc652b..d3861c6 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
@@ -1134,8 +1134,8 @@
capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
int networkResp = 200;
String reason = "";
- listener.onPublish();
cb.onNetworkResponse(networkResp, reason);
+ listener.onPublish();
});
// Unregister the publish state callback
@@ -1278,10 +1278,31 @@
pidfQueue.offer(pidfXml);
int networkResp = 200;
String reason = "";
- listener.onPublish();
cb.onNetworkResponse(networkResp, reason);
+ listener.onPublish();
});
+ LinkedBlockingQueue<ImsRegistrationAttributes> mQueue = new LinkedBlockingQueue<>();
+ RegistrationManager.RegistrationCallback callback =
+ new RegistrationManager.RegistrationCallback() {
+ @Override
+ public void onRegistered(ImsRegistrationAttributes attr) {
+ mQueue.offer(attr);
+ }
+
+ @Override
+ public void onRegistering(ImsRegistrationAttributes attr) {}
+
+ @Override
+ public void onUnregistered(ImsReasonInfo info) {}
+
+ @Override
+ public void onTechnologyChangeFailed(int type, ImsReasonInfo info) {}
+ };
+ ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(imsRcsManager,
+ (m) -> m.registerImsRegistrationCallback(getContext().getMainExecutor(), callback),
+ ImsException.class);
+
// IMS registers
ArraySet<String> featureTags = new ArraySet<>();
// Chat Session
@@ -1290,6 +1311,7 @@
ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder(
ImsRegistrationImplBase.REGISTRATION_TECH_LTE).setFeatureTags(featureTags).build();
sServiceConnector.getCarrierService().getImsRegistration().onRegistered(attr);
+ waitForParam(mQueue, attr);
// Notify framework that the RCS capability status is changed and PRESENCE UCE is enabled.
RcsImsCapabilities capabilities =
@@ -1311,11 +1333,20 @@
publishStateQueue.clear();
// Can not verify the pidf fully, but we can ensure that the service id for the feature is
- // contained in the XML.
- String pidf = waitForResult(pidfQueue);
- assertTrue("PIDF XML doesn't contain chat service-id", pidf.contains(CHAT_SERVICE_ID));
+ // contained in the XML. Multible PUBLISH requests may occur based on the state of the stack
+ // at the time of this call, retry to get correct PIDF up to 5 times.
+ boolean containsChatServiceId = false;
+ boolean containsFileTransferServiceId = false;
+ for (int retry = 0; retry < 5; retry++) {
+ String pidf = waitForResult(pidfQueue);
+ if (pidf == null) break;
+ containsChatServiceId = pidf.contains(CHAT_SERVICE_ID);
+ containsFileTransferServiceId = pidf.contains(FILE_TRANSFER_SERVICE_ID);
+ if (containsChatServiceId && containsFileTransferServiceId) break;
+ }
+ assertTrue("PIDF XML doesn't contain chat service-id", containsChatServiceId);
assertTrue("PIDF XML doesn't contain FT service-id",
- pidf.contains(FILE_TRANSFER_SERVICE_ID));
+ containsFileTransferServiceId);
// Trigger RcsFeature is unavailable
sServiceConnector.getCarrierService().getRcsFeature()
@@ -1378,8 +1409,8 @@
capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
int networkResp = 200;
String reason = "OK";
- listener.onPublish();
cb.onNetworkResponse(networkResp, reason);
+ listener.onPublish();
});
// IMS registers
@@ -1418,8 +1449,8 @@
String reason = "";
int reasonHeaderCause = 400;
String reasonHeaderText = "Bad Request";
- listener.onPublish();
cb.onNetworkResponse(networkResp, reason, reasonHeaderCause, reasonHeaderText);
+ listener.onPublish();
});
// ImsService triggers to notify framework publish device's capabilities.
@@ -1473,8 +1504,8 @@
capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
int networkResp = 200;
String reason = "OK";
- listener.onPublish();
cb.onNetworkResponse(networkResp, reason);
+ listener.onPublish();
});
// Register the callback to listen to the publish state changed
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java
index 4cf7a62..66bcf70 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java
@@ -24,6 +24,7 @@
import android.telephony.ims.RcsContactPresenceTuple;
import android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities;
import android.telephony.ims.RcsContactUceCapability;
+import android.telephony.ims.RcsContactUceCapability.OptionsBuilder;
import android.telephony.ims.RcsContactUceCapability.PresenceBuilder;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -31,13 +32,28 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class RcsContactUceCapabilityTest {
private static final Uri TEST_CONTACT = Uri.fromParts("sip", "me.test", null);
+ public static final String FEATURE_TAG_CHAT_IM =
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcse.im\"";
+
+ public static final String FEATURE_TAG_CHAT_SESSION =
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"";
+
+ public static final String FEATURE_TAG_FILE_TRANSFER =
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.fthttp\"";
+
+ public static final String FEATURE_TAG_POST_CALL =
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.callunanswered\"";
+
@Test
public void testParcelUnparcel() {
if (!ImsUtils.shouldTestImsService()) {
@@ -107,4 +123,44 @@
assertEquals(serviceVersion, unparceledTuple.getServiceVersion());
assertEquals(serviceDescription, unparceledTuple.getServiceDescription());
}
+
+ @Test
+ public void testParcelUnparcelForOptions() {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ final int requestResult = RcsContactUceCapability.REQUEST_RESULT_FOUND;
+ final Set<String> featureTags = new HashSet<>();
+ featureTags.add(FEATURE_TAG_CHAT_IM);
+ featureTags.add(FEATURE_TAG_CHAT_SESSION);
+ featureTags.add(FEATURE_TAG_FILE_TRANSFER);
+
+ OptionsBuilder optionsBuilder = new OptionsBuilder(TEST_CONTACT);
+ optionsBuilder.setRequestResult(requestResult);
+ optionsBuilder.addFeatureTags(featureTags);
+ optionsBuilder.addFeatureTag(FEATURE_TAG_POST_CALL);
+
+ RcsContactUceCapability testCapability = optionsBuilder.build();
+
+ // parcel and unparcel
+ Parcel infoParceled = Parcel.obtain();
+ testCapability.writeToParcel(infoParceled, 0);
+ infoParceled.setDataPosition(0);
+ RcsContactUceCapability unparceledCapability =
+ RcsContactUceCapability.CREATOR.createFromParcel(infoParceled);
+ infoParceled.recycle();
+
+ Set<String> verifiedFeatureTags = new HashSet<>(featureTags);
+ verifiedFeatureTags.add(FEATURE_TAG_POST_CALL);
+
+ int unparceledRequestResult = unparceledCapability.getRequestResult();
+ Set<String> unparceledFeatureTags = unparceledCapability.getFeatureTags();
+ assertEquals(requestResult, unparceledRequestResult);
+ assertEquals(verifiedFeatureTags.size(), unparceledFeatureTags.size());
+ Iterator<String> featureTag = verifiedFeatureTags.iterator();
+ while (featureTag.hasNext()) {
+ assertTrue(unparceledFeatureTags.contains(featureTag.next()));
+ }
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
index aafa6f8..2b81149 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
@@ -65,6 +65,7 @@
import android.telephony.ims.stub.CapabilityExchangeEventListener;
import android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback;
import android.telephony.ims.stub.ImsFeatureConfiguration;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -89,6 +90,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
+import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
@@ -612,6 +614,7 @@
// Verify that the contact capability is received and the onCompleted is called.
RcsContactUceCapability capability = waitForResult(capabilityQueue);
+ assertNotNull("Capabilities were not received for contact: " + sTestNumberUri, capability);
verifyCapabilityResult(capability, sTestNumberUri, REQUEST_RESULT_FOUND, true, true);
waitForResult(completeQueue);
@@ -999,12 +1002,15 @@
// Verify that all the three contact's capabilities are received
RcsContactUceCapability capability = waitForResult(capabilityQueue);
+ assertNotNull("Capabilities were not received for contact: " + contact1, capability);
verifyCapabilityResult(capability, contact1, REQUEST_RESULT_FOUND, true, true);
capability = waitForResult(capabilityQueue);
+ assertNotNull("Capabilities were not received for contact: " + contact2, capability);
verifyCapabilityResult(capability, contact2, REQUEST_RESULT_FOUND, true, false);
capability = waitForResult(capabilityQueue);
+ assertNotNull("Capabilities were not received for contact: " + contact3, capability);
verifyCapabilityResult(capability, contact3, REQUEST_RESULT_FOUND, false, false);
// Verify the onCompleted is called
@@ -1121,15 +1127,15 @@
// Verify the callback "onCapabilitiesReceived" is called.
RcsContactUceCapability capability = waitForResult(capabilityQueue);
- // Verify the callback "onComplete" is called.
- waitForResult(completeQueue);
assertNotNull("RcsContactUceCapability should not be null", capability);
+ // Verify the callback "onComplete" is called.
+ assertNotNull(waitForResult(completeQueue));
assertEquals(RcsContactUceCapability.SOURCE_TYPE_NETWORK, capability.getSourceType());
assertEquals(sTestNumberUri, capability.getContactUri());
assertEquals(RcsContactUceCapability.REQUEST_RESULT_FOUND, capability.getRequestResult());
assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS,
capability.getCapabilityMechanism());
- List<String> resultFeatureTags = capability.getOptionsFeatureTags();
+ Set<String> resultFeatureTags = capability.getFeatureTags();
assertEquals(featureTags.size(), resultFeatureTags.size());
for (String featureTag : featureTags) {
if (!resultFeatureTags.contains(featureTag)) {
@@ -1166,7 +1172,7 @@
assertEquals(RcsContactUceCapability.REQUEST_RESULT_FOUND, capability.getRequestResult());
assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS,
capability.getCapabilityMechanism());
- resultFeatureTags = capability.getOptionsFeatureTags();
+ resultFeatureTags = capability.getFeatureTags();
assertEquals(featureTags.size(), resultFeatureTags.size());
for (String featureTag : featureTags) {
if (!resultFeatureTags.contains(featureTag)) {
@@ -1229,7 +1235,7 @@
sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
final Uri contact = sTestContact2Uri;
- List<String> remoteCapabilities = new ArrayList<>();
+ Set<String> remoteCapabilities = new ArraySet<>();
remoteCapabilities.add(FEATURE_TAG_CHAT);
remoteCapabilities.add(FEATURE_TAG_FILE_TRANSFER);
remoteCapabilities.add(FEATURE_TAG_MMTEL_AUDIO_CALL);
@@ -1284,7 +1290,7 @@
sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
final Uri contact = sTestNumberUri;
- List<String> remoteCapabilities = new ArrayList<>();
+ Set<String> remoteCapabilities = new ArraySet<>();
remoteCapabilities.add(FEATURE_TAG_CHAT);
remoteCapabilities.add(FEATURE_TAG_FILE_TRANSFER);
remoteCapabilities.add(FEATURE_TAG_MMTEL_AUDIO_CALL);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
index 95935ac..84916bc 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
@@ -83,6 +83,9 @@
private static final String FILE_TRANSFER_HTTP_TAG =
"+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gppapplication.ims.iari.rcs.fthttp\"";
+ private static final String[] DEFAULT_FEATURE_TAGS = {
+ ONE_TO_ONE_CHAT_TAG, GROUP_CHAT_TAG, FILE_TRANSFER_HTTP_TAG};
+
private static class CarrierConfigReceiver extends BroadcastReceiver {
private CountDownLatch mLatch = new CountDownLatch(1);
private final int mSubId;
@@ -149,6 +152,8 @@
// APIs.
sServiceConnector.setDeviceSingleRegistrationEnabled(true);
}
+
+ setFeatureTagsCarrierAllowed(DEFAULT_FEATURE_TAGS);
}
@AfterClass
@@ -871,6 +876,41 @@
assertTrue(Arrays.equals(decodedMsg.getContent(), m.getContent()));
}
+ @Test
+ public void testFeatureTagDeniedByCarrierConfig() throws Exception {
+ if (!ImsUtils.shouldTestImsService()) {
+ return;
+ }
+
+ setFeatureTagsCarrierAllowed(new String[]{FILE_TRANSFER_HTTP_TAG});
+ assertTrue(sServiceConnector.setDefaultSmsApp());
+ connectTestImsServiceWithSipTransportAndConfig();
+ TestSipTransport transportImpl = sServiceConnector.getCarrierService().getSipTransport();
+ TestImsRegistration regImpl = sServiceConnector.getCarrierService().getImsRegistration();
+ SipDelegateManager manager = getSipDelegateManager();
+ DelegateRequest request = getDefaultRequest();
+ TestSipDelegateConnection delegateConn = new TestSipDelegateConnection(request);
+ Set<String> deniedTags = new ArraySet<>(request.getFeatureTags());
+ deniedTags.remove(FILE_TRANSFER_HTTP_TAG);
+
+ TestSipDelegate delegate = createSipDelegateConnectionAndVerify(manager, delegateConn,
+ transportImpl, getDeniedTagsForReason(deniedTags,
+ SipDelegateManager.DENIED_REASON_NOT_ALLOWED), 0);
+ assertNotNull(delegate);
+
+ Set<String> registeredTags = new ArraySet<>(
+ Arrays.asList(new String[]{FILE_TRANSFER_HTTP_TAG}));
+ delegateConn.setOperationCountDownLatch(1);
+ DelegateRegistrationState s = getRegisteredRegistrationState(registeredTags);
+ delegate.notifyImsRegistrationUpdate(s);
+ delegateConn.waitForCountDown(ImsUtils.TEST_TIMEOUT_MS);
+ delegateConn.verifyRegistrationStateEquals(s);
+ destroySipDelegateAndVerify(manager, transportImpl, delegateConn, delegate, registeredTags);
+ assertEquals("There should be no more delegates", 0,
+ transportImpl.getDelegates().size());
+ setFeatureTagsCarrierAllowed(getDefaultRequest().getFeatureTags().toArray(new String[0]));
+ }
+
private SipMessage generateSipMessage(String str) {
String crlf = "\r\n";
String[] components = str.split(crlf);
@@ -1151,17 +1191,14 @@
}
private DelegateRequest getDefaultRequest() {
- ArraySet<String> features = new ArraySet<>(3);
- features.add(TestSipTransport.ONE_TO_ONE_CHAT_TAG);
- features.add(TestSipTransport.GROUP_CHAT_TAG);
- features.add(TestSipTransport.FILE_TRANSFER_HTTP_TAG);
+ ArraySet<String> features = new ArraySet<>(Arrays.asList(DEFAULT_FEATURE_TAGS));
return new DelegateRequest(features);
}
private DelegateRequest getChatOnlyRequest() {
ArraySet<String> features = new ArraySet<>(3);
- features.add(TestSipTransport.ONE_TO_ONE_CHAT_TAG);
- features.add(TestSipTransport.GROUP_CHAT_TAG);
+ features.add(ONE_TO_ONE_CHAT_TAG);
+ features.add(GROUP_CHAT_TAG);
return new DelegateRequest(features);
}
@@ -1172,12 +1209,18 @@
.addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
.build();
}
+
private ImsFeatureConfiguration getConfigForRcs() {
return new ImsFeatureConfiguration.Builder()
.addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
.build();
}
+ private Set<FeatureTagState> getDeniedTagsForReason(Set<String> deniedTags, int reason) {
+ return deniedTags.stream().map(t -> new FeatureTagState(t, reason))
+ .collect(Collectors.toSet());
+ }
+
private static void overrideCarrierConfig(PersistableBundle bundle) throws Exception {
CarrierConfigManager carrierConfigManager = InstrumentationRegistry.getInstrumentation()
.getContext().getSystemService(CarrierConfigManager.class);
@@ -1187,6 +1230,13 @@
sReceiver.waitForCarrierConfigChanged();
}
+ private static void setFeatureTagsCarrierAllowed(String[] tags) throws Exception {
+ PersistableBundle bundle = new PersistableBundle();
+ bundle.putStringArray(CarrierConfigManager.Ims.KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY,
+ tags);
+ overrideCarrierConfig(bundle);
+ }
+
private SipDelegateManager getSipDelegateManager() {
ImsManager imsManager = getContext().getSystemService(ImsManager.class);
assertNotNull(imsManager);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
index 032e58a..64ee80c 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsCapabilityExchangeImpl.java
@@ -23,7 +23,7 @@
import android.util.Log;
import java.util.Collection;
-import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -46,7 +46,7 @@
@FunctionalInterface
public interface OptionsOperation {
- void execute(Uri contactUri, List<String> myCapabilities, OptionsResponseCallback callback)
+ void execute(Uri contactUri, Set<String> myCapabilities, OptionsResponseCallback callback)
throws ImsException;
}
@@ -101,7 +101,7 @@
}
@Override
- public void sendOptionsCapabilityRequest(Uri contactUri, List<String> myCapabilities,
+ public void sendOptionsCapabilityRequest(Uri contactUri, Set<String> myCapabilities,
OptionsResponseCallback callback) {
try {
mOptionsOperation.execute(contactUri, myCapabilities, callback);
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ServiceStateTest.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ServiceStateTest.java
index f8f9665..7318e8b 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ServiceStateTest.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ServiceStateTest.java
@@ -16,49 +16,367 @@
package android.telephonyprovider.cts;
+import static android.provider.Telephony.ServiceStateTable.DATA_NETWORK_TYPE;
+import static android.provider.Telephony.ServiceStateTable.DATA_REG_STATE;
+import static android.provider.Telephony.ServiceStateTable.DUPLEX_MODE;
import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import android.Manifest;
import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
import android.provider.Telephony;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
+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.Test;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
@SmallTest
public class ServiceStateTest {
+ private static final int DEFAULT_TIMEOUT = 1000;
+ // Keep the same as ServiceStateProvider#SERVICE_STATE which is NOT same as
+ // Telephony.ServiceStateTable.AUTHORITY;
+ private static final String SERVICE_STATE = "service_state";
+
private ContentResolver mContentResolver;
private TelephonyManager mTelephonyManager;
+ private int mSubId;
+ private ServiceState mInitialServiceState;
@Before
- public void setupTestEnvironment() {
+ public void setUp() {
mContentResolver = getInstrumentation().getContext().getContentResolver();
mTelephonyManager =
getInstrumentation().getContext().getSystemService(TelephonyManager.class);
+ mSubId = SubscriptionManager.getDefaultSubscriptionId();
+ mInitialServiceState = mTelephonyManager.getServiceState();
+ }
+
+ @After
+ public void tearDown() {
+ // Recover the initial ServiceState to remove the impact of manual ServiceState insertion.
+ insertServiceState(mInitialServiceState);
}
/**
- * Asserts that the voice reg state is valid and matches TelephonyManager#getServiceState().
+ * Verifies that the ServiceStateTable CONTENT_URI and AUTHORITY is valid.
*/
@Test
- public void testGetVoiceRegState() {
+ public void testUriAndAuthority() {
Uri uri = Telephony.ServiceStateTable.CONTENT_URI;
assertThat(uri).isEqualTo(Uri.parse("content://service-state/"));
- Cursor cursor = mContentResolver.query(uri, new String[] {VOICE_REG_STATE}, null, null);
- assertThat(cursor.getCount()).isEqualTo(1);
- cursor.moveToNext();
+ String authority = Telephony.ServiceStateTable.AUTHORITY;
+ assertThat(authority).isEqualTo("service-state");
+ }
- int voiceRegState = cursor.getInt(cursor.getColumnIndex(VOICE_REG_STATE));
- assertThat(voiceRegState).isEqualTo(mTelephonyManager.getServiceState().getState());
+ /**
+ * Verifies that the voice reg state is valid and matches ServiceState#getState().
+ */
+ @Test
+ public void testGetVoiceRegState_query() {
+ try (Cursor cursor = mContentResolver.query(Telephony.ServiceStateTable.CONTENT_URI,
+ new String[]{VOICE_REG_STATE}, null, null)) {
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+
+ int voiceRegState = cursor.getInt(cursor.getColumnIndex(VOICE_REG_STATE));
+ assertThat(voiceRegState).isEqualTo(mTelephonyManager.getServiceState().getState());
+ }
+ }
+
+ /**
+ * Verifies that when voice reg state did not change, the observer should not receive the
+ * notification.
+ */
+ @Test
+ public void testGetVoiceRegState_noChangeObserved() throws Exception {
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+ oldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
+
+ ServiceState copyOfOldSS = new ServiceState();
+ copyOfOldSS.setStateOutOfService();
+ copyOfOldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
+ // set additional fields which is not related to voice reg state
+ copyOfOldSS.setChannelNumber(65536);
+ copyOfOldSS.setIsManualSelection(true);
+
+ verifyNotificationObservedWhenFieldChanged(
+ VOICE_REG_STATE, oldSS, copyOfOldSS, false /*expectChange*/);
+ }
+
+ /**
+ * Verifies that when voice reg state changed, the observer should receive the notification.
+ */
+ @Test
+ public void testGetVoiceRegState_changeObserved() throws Exception {
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+ oldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
+
+ ServiceState newSS = new ServiceState();
+ newSS.setStateOutOfService();
+ newSS.setState(ServiceState.STATE_POWER_OFF);
+
+ verifyNotificationObservedWhenFieldChanged(
+ VOICE_REG_STATE, oldSS, newSS, true /*expectChange*/);
+ }
+
+ /**
+ * Verifies that the data network type is valid and matches ServiceState#getDataNetworkType()
+ */
+ @Test
+ public void testGetDataNetworkType_query() {
+ try (Cursor cursor = mContentResolver.query(Telephony.ServiceStateTable.CONTENT_URI,
+ new String[]{DATA_NETWORK_TYPE}, null, null)) {
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+
+ int dataNetworkType = cursor.getInt(cursor.getColumnIndex(DATA_NETWORK_TYPE));
+ assertThat(dataNetworkType).isEqualTo(
+ mTelephonyManager.getServiceState().getDataNetworkType());
+ }
+ }
+
+ /**
+ * Verifies that when data network type did not change, the observer should not receive the
+ * notification.
+ */
+ @Test
+ public void testDataNetworkType_noChangeObserved() throws Exception {
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+
+ ServiceState copyOfOldSS = new ServiceState();
+ copyOfOldSS.setStateOutOfService();
+
+ // Add a DOMAIN_CS NRI which should not update DataNetworkType
+ NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+ .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
+ .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_GPRS)
+ .setRegistrationState(REGISTRATION_STATE_HOME)
+ .build();
+ copyOfOldSS.addNetworkRegistrationInfo(nri);
+
+ verifyNotificationObservedWhenFieldChanged(
+ DATA_NETWORK_TYPE, oldSS, copyOfOldSS, false /*expectChange*/);
+ }
+
+ /**
+ * Verifies that when data network type changed, the observer should receive the notification.
+ */
+ @Test
+ public void testDataNetworkType_changeObserved() throws Exception {
+ // While we don't have a method to directly set dataNetworkType, we emulate a ServiceState
+ // change that will trigger the change of dataNetworkType, according to the logic in
+ // ServiceState#getDataNetworkType()
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+
+ ServiceState newSS = new ServiceState();
+ newSS.setStateOutOfService();
+
+ NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+ .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+ .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+ .setRegistrationState(REGISTRATION_STATE_HOME)
+ .build();
+ newSS.addNetworkRegistrationInfo(nri);
+
+ verifyNotificationObservedWhenFieldChanged(
+ DATA_NETWORK_TYPE, oldSS, newSS, true /*expectChange*/);
+ }
+
+ /**
+ * Verifies that the duplex mode is valid and matches ServiceState#getDuplexMode().
+ */
+ @Test
+ public void testGetDuplexMode_query() {
+ try (Cursor cursor = mContentResolver.query(Telephony.ServiceStateTable.CONTENT_URI,
+ new String[]{DUPLEX_MODE}, null, null)) {
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+
+ int duplexMode = cursor.getInt(cursor.getColumnIndex(DUPLEX_MODE));
+ assertThat(duplexMode).isEqualTo(mTelephonyManager.getServiceState().getDuplexMode());
+ }
+ }
+
+ /**
+ * Verifies that even we have duplex mode change, the observer should not receive the
+ * notification (duplex mode is a poll-only field).
+ */
+ @Test
+ public void testGetDuplexMode_noChangeObserved() throws Exception {
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+
+ ServiceState newSS = new ServiceState();
+ newSS.setStateOutOfService();
+
+ // Add NRI to trigger SS with duplex mode updated
+ NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+ .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+ .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+ .build();
+ newSS.addNetworkRegistrationInfo(nri);
+ newSS.setChannelNumber(65536); // EutranBand.BAND_65, DUPLEX_MODE_FDD
+
+ verifyNotificationObservedWhenFieldChanged(
+ DUPLEX_MODE, oldSS, newSS, false /*expectChange*/);
+ }
+
+ /**
+ * Verifies that the data reg state is valid and matches ServiceState#getDataRegState()
+ */
+ @Test
+ public void testGetDataRegState_query() {
+ try (Cursor cursor = mContentResolver.query(Telephony.ServiceStateTable.CONTENT_URI,
+ new String[]{DATA_REG_STATE}, null, null)) {
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+
+ int dataRegState = cursor.getInt(cursor.getColumnIndex(DATA_REG_STATE));
+ assertThat(dataRegState).isEqualTo(
+ mTelephonyManager.getServiceState().getDataRegState());
+ }
+ }
+
+ /**
+ * Verifies that when data reg state did not change, the observer should not receive the
+ * notification.
+ */
+ @Test
+ public void testGetDataRegState_noChangeObserved() throws Exception {
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+ oldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
+
+ ServiceState copyOfOldSS = new ServiceState();
+ copyOfOldSS.setStateOutOfService();
+ copyOfOldSS.setState(ServiceState.STATE_OUT_OF_SERVICE);
+ // set additional fields which is not related to data reg state
+ copyOfOldSS.setChannelNumber(65536);
+ copyOfOldSS.setIsManualSelection(true);
+
+ verifyNotificationObservedWhenFieldChanged(
+ DATA_REG_STATE, oldSS, copyOfOldSS, false /*expectChange*/);
+ }
+
+ /**
+ * Verifies that when data reg state changed, the observer should receive the notification.
+ */
+ @Test
+ public void testGetDataRegState_changeObserved() throws Exception {
+ ServiceState oldSS = new ServiceState();
+ oldSS.setStateOutOfService();
+
+ ServiceState newSS = new ServiceState();
+ newSS.setStateOutOfService();
+ newSS.setStateOff();
+
+ verifyNotificationObservedWhenFieldChanged(
+ DATA_REG_STATE, oldSS, newSS, true /*expectChange*/);
+ }
+
+ /**
+ * Insert new ServiceState over the old ServiceState and expect the observer receiving the
+ * notification over the observed field change.
+ */
+ private void verifyNotificationObservedWhenFieldChanged(String field, ServiceState oldSS,
+ ServiceState newSS, boolean expectChange) throws Exception {
+ final Uri uriForSubAndField =
+ Telephony.ServiceStateTable.getUriForSubscriptionIdAndField(mSubId, field);
+ insertServiceState(oldSS);
+
+ RecordingContentObserver observer = new RecordingContentObserver();
+ mContentResolver.registerContentObserver(uriForSubAndField, false, observer);
+ assertWithMessage("Observer is NOT empty in the beginning.").that(
+ observer.mObserved).isEmpty();
+
+ insertServiceState(newSS);
+
+ if (expectChange) {
+ // Only verify we did receive the notification for the expected field, instead of the
+ // number of notifications we received to remove flakiness for different cases.
+ PollingCheck.check(
+ "Expect notification when " + field + " updated.",
+ DEFAULT_TIMEOUT, () -> observer.mObserved.contains(uriForSubAndField));
+ } else {
+ // Let the bullets fly for a while before we check the target.
+ try {
+ Thread.sleep(DEFAULT_TIMEOUT);
+ } catch (InterruptedException ignored) {
+ }
+
+ // Fields in ServiceState are not orthogonal. In case we do receive notification(s),
+ // further check if it is for the expected field.
+ assertWithMessage("Unexpected notification for " + field).that(
+ observer.mObserved).doesNotContain(uriForSubAndField);
+
+ }
+
+ mContentResolver.unregisterContentObserver(observer);
+ }
+
+ // Manually insert the ServiceState into table to test the notification.
+ private void insertServiceState(ServiceState state) {
+ ContentValues values = getContentValuesForServiceState(state);
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mContentResolver.insert(
+ Telephony.ServiceStateTable.getUriForSubscriptionId(mSubId), values),
+ Manifest.permission.MODIFY_PHONE_STATE);
+ }
+
+ // Copied from ServiceStateProvider#getContentValuesForServiceState
+ private static ContentValues getContentValuesForServiceState(ServiceState state) {
+ ContentValues values = new ContentValues();
+ final Parcel p = Parcel.obtain();
+ state.writeToParcel(p, 0);
+ values.put(SERVICE_STATE, p.marshall());
+ return values;
+ }
+
+ private static class RecordingContentObserver extends ContentObserver {
+ List<Uri> mObserved = new CopyOnWriteArrayList<>();
+
+ RecordingContentObserver() {
+ super(new Handler(Looper.getMainLooper()));
+ }
+
+ @Override
+ public void onChange(boolean selfChange, @Nullable Uri uri) {
+ mObserved.add(uri);
+ }
}
}
diff --git a/tests/tests/time/src/android/time/cts/TimeManagerTest.java b/tests/tests/time/src/android/time/cts/TimeManagerTest.java
index cf59f48..94ab334 100644
--- a/tests/tests/time/src/android/time/cts/TimeManagerTest.java
+++ b/tests/tests/time/src/android/time/cts/TimeManagerTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import android.app.time.Capabilities;
import android.app.time.TimeManager;
import android.app.time.TimeZoneCapabilities;
import android.app.time.TimeZoneCapabilitiesAndConfig;
@@ -86,7 +87,7 @@
.setAutoDetectionEnabled(newAutoDetectionEnabledValue)
.build();
if (capabilities.getConfigureAutoDetectionEnabledCapability()
- >= TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE) {
+ >= Capabilities.CAPABILITY_NOT_APPLICABLE) {
assertTrue(timeManager.updateTimeZoneConfiguration(configUpdate));
expectedListenerTriggerCount++;
waitForListenerCallbackCount(
@@ -112,7 +113,7 @@
.setGeoDetectionEnabled(newGeoDetectionEnabledValue)
.build();
if (capabilities.getConfigureGeoDetectionEnabledCapability()
- >= TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE) {
+ >= Capabilities.CAPABILITY_NOT_APPLICABLE) {
assertTrue(timeManager.updateTimeZoneConfiguration(configUpdate));
expectedListenerTriggerCount++;
waitForListenerCallbackCount(
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java
index a6b1761..c86090a 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/EdgeEffectTests.java
@@ -479,6 +479,20 @@
assertEquals(distanceAfterAnimation, edgeEffect.getDistance(), 0f);
}
+ /**
+ * When an EdgeEffect with TYPE_STRETCH is drawn on a non-RecordingCanvas, the animation
+ * should immediately end.
+ */
+ @Test
+ public void testStretchOnBitmapCanvas() throws Throwable {
+ EdgeEffect edgeEffect = createEdgeEffectWithPull(EdgeEffect.TYPE_STRETCH);
+ Bitmap bitmap = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ edgeEffect.draw(canvas);
+ assertTrue(edgeEffect.isFinished());
+ assertEquals(0f, edgeEffect.getDistance(), 0f);
+ }
+
private EdgeEffect createEdgeEffectWithPull(int edgeEffectType) {
EdgeEffect edgeEffect = new EdgeEffect(getContext());
edgeEffect.setType(edgeEffectType);
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
index 83b09c1..a243bb3 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
@@ -218,12 +218,16 @@
Bitmap idealBitmap = captureRenderSpec(mTestCases.remove(0));
- for (TestCase testCase : mTestCases) {
- Bitmap testCaseBitmap = captureRenderSpec(testCase);
- mBitmapAsserter.assertBitmapsAreSimilar(idealBitmap, testCaseBitmap, bitmapComparer,
- getName(), testCase.getDebugString());
+ try {
+ for (TestCase testCase : mTestCases) {
+ Bitmap testCaseBitmap = captureRenderSpec(testCase);
+ mBitmapAsserter.assertBitmapsAreSimilar(idealBitmap, testCaseBitmap,
+ bitmapComparer,
+ getName(), testCase.getDebugString());
+ }
+ } finally {
+ getActivity().reset();
}
- getActivity().reset();
}
/**
@@ -235,12 +239,15 @@
throw new IllegalStateException("Need at least one test to run");
}
- for (TestCase testCase : mTestCases) {
- Bitmap testCaseBitmap = captureRenderSpec(testCase);
- mBitmapAsserter.assertBitmapIsVerified(testCaseBitmap, bitmapVerifier,
- getName(), testCase.getDebugString());
+ try {
+ for (TestCase testCase : mTestCases) {
+ Bitmap testCaseBitmap = captureRenderSpec(testCase);
+ mBitmapAsserter.assertBitmapIsVerified(testCaseBitmap, bitmapVerifier,
+ getName(), testCase.getDebugString());
+ }
+ } finally {
+ getActivity().reset();
}
- getActivity().reset();
}
private static final int VERIFY_ANIMATION_LOOP_COUNT = 20;
@@ -258,21 +265,24 @@
throw new IllegalStateException("Need at least one test to run");
}
- for (TestCase testCase : mTestCases) {
- TestPositionInfo testPositionInfo = runRenderSpec(testCase);
+ try {
+ for (TestCase testCase : mTestCases) {
+ TestPositionInfo testPositionInfo = runRenderSpec(testCase);
- for (int i = 0; i < VERIFY_ANIMATION_LOOP_COUNT; i++) {
- try {
- Thread.sleep(VERIFY_ANIMATION_SLEEP_MS);
- } catch (InterruptedException e) {
- e.printStackTrace();
+ for (int i = 0; i < VERIFY_ANIMATION_LOOP_COUNT; i++) {
+ try {
+ Thread.sleep(VERIFY_ANIMATION_SLEEP_MS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ Bitmap testCaseBitmap = takeScreenshot(testPositionInfo);
+ mBitmapAsserter.assertBitmapIsVerified(testCaseBitmap, bitmapVerifier,
+ getName(), testCase.getDebugString());
}
- Bitmap testCaseBitmap = takeScreenshot(testPositionInfo);
- mBitmapAsserter.assertBitmapIsVerified(testCaseBitmap, bitmapVerifier,
- getName(), testCase.getDebugString());
}
+ } finally {
+ getActivity().reset();
}
- getActivity().reset();
}
/**
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java
index adc1b06..b5f7d76 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java
@@ -87,6 +87,16 @@
return new File(testDirectory, testName + "_" + type + ".png");
}
+ private static String bypassContentProvider(File file) {
+ // TradeFed currently insists on bouncing off of a content provider for the path
+ // we are using, but that content provider will never have permissions
+ // Since we want to avoid needing to use requestLegacyStorage & there's currently no
+ // option to tell TF to not use the content provider, just break its file
+ // detection pattern
+ // b/183140644
+ return "/." + file.getAbsolutePath();
+ }
+
/**
* Saves two files, one the capture of an ideal drawing, and one the capture of the tested
* drawing. The third file saved is a bitmap that is returned from the given visualizer's
@@ -116,9 +126,10 @@
saveBitmap(visualizerBitmap, visualizerFile);
Bundle report = new Bundle();
- report.putString(KEY_PREFIX + TYPE_IDEAL_RENDERING, idealFile.getAbsolutePath());
- report.putString(KEY_PREFIX + TYPE_TESTED_RENDERING, testedFile.getAbsolutePath());
- report.putString(KEY_PREFIX + TYPE_VISUALIZER_RENDERING, visualizerFile.getAbsolutePath());
+ report.putString(KEY_PREFIX + TYPE_IDEAL_RENDERING, bypassContentProvider(idealFile));
+ report.putString(KEY_PREFIX + TYPE_TESTED_RENDERING, bypassContentProvider(testedFile));
+ report.putString(KEY_PREFIX + TYPE_VISUALIZER_RENDERING,
+ bypassContentProvider(visualizerFile));
sInstrumentation.sendStatus(INST_STATUS_IN_PROGRESS, report);
}
@@ -130,7 +141,7 @@
File capture = getFile(className, testName, TYPE_SINGULAR);
saveBitmap(bitmap, capture);
Bundle report = new Bundle();
- report.putString(KEY_PREFIX + TYPE_SINGULAR, capture.getAbsolutePath());
+ report.putString(KEY_PREFIX + TYPE_SINGULAR, bypassContentProvider(capture));
sInstrumentation.sendStatus(INST_STATUS_IN_PROGRESS, report);
}
diff --git a/tests/tests/uirendering27/res/values/themes.xml b/tests/tests/uirendering27/res/values/themes.xml
index e75376b..fa5c591 100644
--- a/tests/tests/uirendering27/res/values/themes.xml
+++ b/tests/tests/uirendering27/res/values/themes.xml
@@ -19,6 +19,7 @@
<item name="android:windowFullscreen">true</item>
<item name="android:windowOverscan">true</item>
<item name="android:fadingEdge">none</item>
+ <item name="android:windowDisablePreview">true</item>
<item name="android:windowBackground">@android:color/white</item>
<item name="android:windowContentTransitions">false</item>
<item name="android:windowAnimationStyle">@null</item>
diff --git a/tests/tests/util/src/android/util/cts/SparseArrayTest.java b/tests/tests/util/src/android/util/cts/SparseArrayTest.java
index ce4382b..b90fd38 100644
--- a/tests/tests/util/src/android/util/cts/SparseArrayTest.java
+++ b/tests/tests/util/src/android/util/cts/SparseArrayTest.java
@@ -16,6 +16,8 @@
package android.util.cts;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -140,7 +142,7 @@
assertEquals(LENGTH, sparseArray.size());
assertEquals(VALUE_FOR_NON_EXISTED_KEY,
- sparseArray.get(NON_EXISTED_KEY, VALUE_FOR_NON_EXISTED_KEY));
+ sparseArray.get(NON_EXISTED_KEY, VALUE_FOR_NON_EXISTED_KEY));
assertNull(sparseArray.get(NON_EXISTED_KEY)); // the default value is null
int size = sparseArray.size();
@@ -196,6 +198,27 @@
}
@Test
+ public void testSet() {
+ SparseArray<String> first = new SparseArray<>();
+ first.put(0, "0");
+ first.put(1, "1");
+ first.put(2, "2");
+
+ SparseArray<String> second = new SparseArray<>();
+ second.set(2, "2");
+ second.set(0, "0");
+ second.set(1, "1");
+
+ assertThat(first.size()).isEqualTo(second.size());
+ assertThat(first.get(0)).isEqualTo(second.get(0));
+ assertThat(first.get(1)).isEqualTo(second.get(1));
+ assertThat(first.get(2)).isEqualTo(second.get(2));
+ assertThat(first.get(3, "-1")).isEqualTo(second.get(3, "-1"));
+
+ testContentEquals(first, second, SparseArray::contentEquals);
+ }
+
+ @Test
public void testContentEquals() {
SparseArray<TestData> first = new SparseArray<>();
first.put(0, new TestData("0"));
diff --git a/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java b/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
index 81e0a01..71d6835 100644
--- a/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
+++ b/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
@@ -232,14 +232,14 @@
/** Test implementation of VcnStatusCallback for verification purposes. */
private static class TestVcnStatusCallback extends VcnManager.VcnStatusCallback {
- private final CompletableFuture<Integer> mFutureOnVcnStatusChanged =
+ private final CompletableFuture<Integer> mFutureOnStatusChanged =
new CompletableFuture<>();
private final CompletableFuture<GatewayConnectionError> mFutureOnGatewayConnectionError =
new CompletableFuture<>();
@Override
- public void onVcnStatusChanged(int statusCode) {
- mFutureOnVcnStatusChanged.complete(statusCode);
+ public void onStatusChanged(int statusCode) {
+ mFutureOnStatusChanged.complete(statusCode);
}
@Override
@@ -249,8 +249,8 @@
new GatewayConnectionError(networkCapabilities, errorCode, detail));
}
- public int awaitOnVcnStatusChanged() throws Exception {
- return mFutureOnVcnStatusChanged.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ public int awaitOnStatusChanged() throws Exception {
+ return mFutureOnStatusChanged.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public GatewayConnectionError awaitOnGatewayConnectionError() throws Exception {
@@ -289,7 +289,7 @@
try {
registerVcnStatusCallbackForSubId(callback, subId);
- final int statusCode = callback.awaitOnVcnStatusChanged();
+ final int statusCode = callback.awaitOnStatusChanged();
assertEquals(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED, statusCode);
} finally {
mVcnManager.unregisterVcnStatusCallback(callback);
diff --git a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
index 49626a8..c5bd894 100644
--- a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
@@ -54,7 +54,8 @@
desc.width = width;
desc.height = height;
desc.layers = 1;
- desc.usage = AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
+ desc.usage = AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+ AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE;
desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
AHardwareBuffer_allocate(&desc, &buffer);
@@ -278,6 +279,28 @@
reinterpret_cast<ASurfaceControl*>(surfaceControl), src, dst, transform);
}
+void SurfaceTransaction_setSourceRect(JNIEnv* /*env*/, jclass, jlong surfaceControl,
+ jlong surfaceTransaction, jint srcLeft, jint srcTop,
+ jint srcRight, jint srcBottom) {
+ const ARect src{srcLeft, srcTop, srcRight, srcBottom};
+ ASurfaceTransaction_setSourceRect(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+ reinterpret_cast<ASurfaceControl*>(surfaceControl), src);
+}
+
+void SurfaceTransaction_setPosition(JNIEnv* /*env*/, jclass, jlong surfaceControl,
+ jlong surfaceTransaction, jint dstLeft, jint dstTop,
+ jint dstRight, jint dstBottom) {
+ const ARect dst{dstLeft, dstTop, dstRight, dstBottom};
+ ASurfaceTransaction_setPosition(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+ reinterpret_cast<ASurfaceControl*>(surfaceControl), dst);
+}
+
+void SurfaceTransaction_setTransform(JNIEnv* /*env*/, jclass, jlong surfaceControl,
+ jlong surfaceTransaction, jint transform) {
+ ASurfaceTransaction_setTransform(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+ reinterpret_cast<ASurfaceControl*>(surfaceControl), transform);
+}
+
void SurfaceTransaction_setDamageRegion(JNIEnv* /*env*/, jclass,
jlong surfaceControl,
jlong surfaceTransaction, jint left,
diff --git a/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java b/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
index 52054f5..8bc2f48 100644
--- a/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
+++ b/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
@@ -20,17 +20,6 @@
import static org.junit.Assert.assertTrue;
-import androidx.test.filters.RequiresDevice;
-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.TestName;
-import org.junit.runner.RunWith;
-
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
@@ -49,6 +38,19 @@
import android.view.cts.surfacevalidator.PixelColor;
import android.view.cts.surfacevalidator.SurfaceControlTestCase;
+import androidx.test.filters.RequiresDevice;
+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.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
import java.util.HashSet;
import java.util.Set;
@@ -59,6 +61,13 @@
System.loadLibrary("ctsview_jni");
}
+ @Parameter(0) public boolean mTestSetGeometry;
+
+ @Parameters
+ public static Object[] data() {
+ return new Boolean[] {false, true};
+ }
+
private static final String TAG = ASurfaceControlTest.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -209,9 +218,16 @@
public void setGeometry(long surfaceControl, long surfaceTransaction, int srcLeft,
int srcTop, int srcRight, int srcBottom, int dstLeft, int dstTop, int dstRight,
int dstBottom, int transform) {
- nSurfaceTransaction_setGeometry(
- surfaceControl, surfaceTransaction, srcLeft, srcTop, srcRight, srcBottom,
- dstLeft, dstTop, dstRight, dstBottom, transform);
+ if (mTestSetGeometry) {
+ nSurfaceTransaction_setGeometry(surfaceControl, surfaceTransaction, srcLeft, srcTop,
+ srcRight, srcBottom, dstLeft, dstTop, dstRight, dstBottom, transform);
+ } else {
+ nSurfaceTransaction_setPosition(
+ surfaceControl, surfaceTransaction, dstLeft, dstTop, dstRight, dstBottom);
+ nSurfaceTransaction_setTransform(surfaceControl, surfaceTransaction, transform);
+ nSurfaceTransaction_setSourceRect(
+ surfaceControl, surfaceTransaction, srcLeft, srcTop, srcRight, srcBottom);
+ }
}
public void setGeometry(long surfaceControl, int srcLeft, int srcTop, int srcRight,
@@ -1596,6 +1612,12 @@
private static native void nSurfaceTransaction_setGeometry(
long surfaceControl, long surfaceTransaction, int srcRight, int srcTop, int srcLeft,
int srcBottom, int dstRight, int dstTop, int dstLeft, int dstBottom, int transform);
+ private static native void nSurfaceTransaction_setSourceRect(long surfaceControl,
+ long surfaceTransaction, int srcRight, int srcTop, int srcLeft, int srcBottom);
+ private static native void nSurfaceTransaction_setPosition(long surfaceControl,
+ long surfaceTransaction, int dstRight, int dstTop, int dstLeft, int dstBottom);
+ private static native void nSurfaceTransaction_setTransform(
+ long surfaceControl, long surfaceTransaction, int transform);
private static native void nSurfaceTransaction_setDamageRegion(
long surfaceControl, long surfaceTransaction, int right, int top, int left, int bottom);
private static native void nSurfaceTransaction_setZOrder(
diff --git a/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
index 6ee1441..c6476bf 100644
--- a/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
+++ b/tests/tests/view/src/android/view/cts/FrameMetricsListenerTest.java
@@ -209,8 +209,11 @@
assertTrue(intended_vsync < now);
assertTrue(vsync < now);
assertTrue(vsync >= intended_vsync);
+
+ // swapBuffers and gpuDuration may happen in parallel, so instead of counting both we need
+ // to take the longer of the two.
assertTrue(totalDuration >= unknownDelay + input + animation + layoutMeasure + draw + sync
- + commandIssue + swapBuffers + gpuDuration);
+ + commandIssue + Math.max(gpuDuration, swapBuffers));
// This is the only boolean metric so far
final long firstDrawFrameMetric = frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME);
diff --git a/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java b/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
index edfb312..49a9fc8 100644
--- a/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewOnReceiveContentTest.java
@@ -17,18 +17,23 @@
package android.view.cts;
import static android.view.ContentInfo.SOURCE_CLIPBOARD;
+import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ClipData;
import android.content.ClipDescription;
import android.net.Uri;
import android.view.ContentInfo;
+import android.view.DragEvent;
import android.view.OnReceiveContentListener;
import android.view.View;
@@ -40,6 +45,10 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.stubbing.Answer;
+
+import java.util.Objects;
/**
* Tests for {@link View#performReceiveContent} and related code.
@@ -105,8 +114,8 @@
public void testPerformReceiveContent() {
View view = new View(mActivity);
String[] mimeTypes = new String[] {"image/*", "video/mp4"};
- ContentInfo samplePayloadGif = sampleUriPayload("image/gif");
- ContentInfo samplePayloadPdf = sampleUriPayload("application/pdf");
+ ContentInfo samplePayloadGif = sampleUriPayload(SOURCE_CLIPBOARD, "image/gif");
+ ContentInfo samplePayloadPdf = sampleUriPayload(SOURCE_CLIPBOARD, "application/pdf");
// Calling performReceiveContent() returns the payload if there's no listener (default)
assertThat(view.performReceiveContent(samplePayloadGif)).isEqualTo(samplePayloadGif);
@@ -129,7 +138,7 @@
public void testOnReceiveContent() {
View view = new View(mActivity);
String[] mimeTypes = new String[] {"image/*", "video/mp4"};
- ContentInfo samplePayloadGif = sampleUriPayload("image/gif");
+ ContentInfo samplePayloadGif = sampleUriPayload(SOURCE_CLIPBOARD, "image/gif");
// Calling onReceiveContent() returns the payload if there's no listener
assertThat(view.performReceiveContent(samplePayloadGif)).isEqualTo(samplePayloadGif);
@@ -140,10 +149,112 @@
assertThat(view.onReceiveContent(samplePayloadGif)).isEqualTo(samplePayloadGif);
}
- private static ContentInfo sampleUriPayload(String ... mimeTypes) {
+ @Test
+ public void testOnDragEvent_noOnReceiveContentListener() {
+ View view = new View(mActivity);
+
+ DragEvent dragEvent = mock(DragEvent.class);
+ when(dragEvent.getAction()).thenReturn(DragEvent.ACTION_DRAG_STARTED);
+ assertThat(view.onDragEvent(dragEvent)).isFalse();
+
+ when(dragEvent.getAction()).thenReturn(DragEvent.ACTION_DROP);
+ assertThat(view.onDragEvent(dragEvent)).isFalse();
+ }
+
+ @Test
+ public void testOnDragEvent_withOnReceiveContentListener() {
+ View view = new View(mActivity);
+ String[] mimeTypes = new String[] {"text/*", "image/*", "video/mp4"};
+ view.setOnReceiveContentListener(mimeTypes, mReceiver);
+ when(mReceiver.onReceiveContent(any(), any())).thenReturn(null);
+
+ // For an ACTION_DRAG_STARTED, we expect true to be returned (no class to the listener yet).
+ DragEvent dragEvent = mock(DragEvent.class);
+ when(dragEvent.getAction()).thenReturn(DragEvent.ACTION_DRAG_STARTED);
+ assertThat(view.onDragEvent(dragEvent)).isTrue();
+
+ // For an ACTION_DROP, we expect the listener to be invoked with the content from the drag
+ // event.
+ when(dragEvent.getAction()).thenReturn(DragEvent.ACTION_DROP);
+ ClipData clip = new ClipData(
+ new ClipDescription("test", new String[] {"image/jpeg"}),
+ new ClipData.Item(Uri.parse("content://example/1")));
+ when(dragEvent.getClipData()).thenReturn(clip);
+ assertThat(view.onDragEvent(dragEvent)).isTrue();
+ verify(mReceiver).onReceiveContent(same(view), contentEq(clip, SOURCE_DRAG_AND_DROP, 0));
+ }
+
+ @Test
+ public void testOnDragEvent_withOnReceiveContentListener_noneOfTheContentAccepted() {
+ View view = new View(mActivity);
+ String[] mimeTypes = new String[] {"text/*", "image/*"};
+ view.setOnReceiveContentListener(mimeTypes, mReceiver);
+ when(mReceiver.onReceiveContent(same(view), any(ContentInfo.class))).thenAnswer(
+ (Answer<ContentInfo>) invocation -> invocation.getArgument(1));
+
+ // When the return value from OnReceiveContentListener.onReceiveContent is the same
+ // payload instance that was passed into it, View.onDragEvent should return false.
+ DragEvent dragEvent = mock(DragEvent.class);
+ when(dragEvent.getAction()).thenReturn(DragEvent.ACTION_DROP);
+ ClipData clip = new ClipData(
+ new ClipDescription("test", new String[] {"video/mp4"}),
+ new ClipData.Item(Uri.parse("content://example/1")));
+ when(dragEvent.getClipData()).thenReturn(clip);
+ assertThat(view.onDragEvent(dragEvent)).isFalse();
+ verify(mReceiver).onReceiveContent(same(view), contentEq(clip, SOURCE_DRAG_AND_DROP, 0));
+ }
+
+ @Test
+ public void testOnDragEvent_withOnReceiveContentListener_someOfTheContentAccepted() {
+ View view = new View(mActivity);
+ String[] mimeTypes = new String[] {"text/*", "image/*"};
+ view.setOnReceiveContentListener(mimeTypes, mReceiver);
+ when(mReceiver.onReceiveContent(same(view), any(ContentInfo.class))).thenReturn(
+ sampleUriPayload(SOURCE_DRAG_AND_DROP, "video/mp4"));
+
+ // When the return value from OnReceiveContentListener.onReceiveContent is not the same
+ // payload instance that was passed into it, View.onDragEvent should return true.
+ DragEvent dragEvent = mock(DragEvent.class);
+ when(dragEvent.getAction()).thenReturn(DragEvent.ACTION_DROP);
+ ClipData clip = new ClipData(
+ new ClipDescription("test", new String[] {"video/mp4"}),
+ new ClipData.Item(Uri.parse("content://example/1")));
+ when(dragEvent.getClipData()).thenReturn(clip);
+ assertThat(view.onDragEvent(dragEvent)).isTrue();
+ verify(mReceiver).onReceiveContent(same(view), contentEq(clip, SOURCE_DRAG_AND_DROP, 0));
+ }
+
+ private static ContentInfo sampleUriPayload(int source, String ... mimeTypes) {
ClipData clip = new ClipData(
new ClipDescription("test", mimeTypes),
new ClipData.Item(Uri.parse("content://example/1")));
- return new ContentInfo.Builder(clip, SOURCE_CLIPBOARD).build();
+ return new ContentInfo.Builder(clip, source).build();
+ }
+
+ private static ContentInfo contentEq(ClipData clip, int source, int flags) {
+ return argThat(new ContentInfoArgumentMatcher(clip, source, flags));
+ }
+
+ private static class ContentInfoArgumentMatcher implements ArgumentMatcher<ContentInfo> {
+ private final ClipData mClip;
+ private final int mSource;
+ private final int mFlags;
+
+ private ContentInfoArgumentMatcher(ClipData clip, int source, int flags) {
+ mClip = clip;
+ mSource = source;
+ mFlags = flags;
+ }
+
+ @Override
+ public boolean matches(ContentInfo actual) {
+ ClipData.Item expectedItem = mClip.getItemAt(0);
+ ClipData.Item actualItem = actual.getClip().getItemAt(0);
+ return Objects.equals(expectedItem.getText(), actualItem.getText())
+ && Objects.equals(expectedItem.getUri(), actualItem.getUri())
+ && mSource == actual.getSource()
+ && mFlags == actual.getFlags()
+ && actual.getExtras() == null;
+ }
}
}
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 b9079e9..c8fa63c 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
@@ -151,7 +151,6 @@
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
startActivityForResult(mProjectionManager.createScreenCaptureIntent(), PERMISSION_CODE);
- dismissPermissionDialog();
mProjectionServiceBound = true;
}
diff --git a/tests/tests/voiceinteraction/Android.bp b/tests/tests/voiceinteraction/Android.bp
index 0f4a568..df37450 100644
--- a/tests/tests/voiceinteraction/Android.bp
+++ b/tests/tests/voiceinteraction/Android.bp
@@ -23,9 +23,17 @@
"CtsVoiceInteractionCommon",
"ctstestrunner-axt",
"compatibility-device-util-axt",
- "androidx.test.ext.junit",
+ "androidx.test.ext.junit",
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java",
+ "service/src/android/voiceinteraction/service/MainHotwordDetectionService.java",
+ "service/src/android/voiceinteraction/service/MainInteractionService.java",
+ "service/src/android/voiceinteraction/service/MainInteractionSession.java",
+ "service/src/android/voiceinteraction/service/MainInteractionSessionService.java",
+ "service/src/android/voiceinteraction/service/MainRecognitionService.java"
+ ],
// Tag this module as a cts test artifact
test_suites: [
"cts",
diff --git a/tests/tests/voiceinteraction/AndroidManifest.xml b/tests/tests/voiceinteraction/AndroidManifest.xml
index 77b20cb..9d7839e 100644
--- a/tests/tests/voiceinteraction/AndroidManifest.xml
+++ b/tests/tests/voiceinteraction/AndroidManifest.xml
@@ -44,6 +44,50 @@
android:label="Voice Interaction Service Activity"
android:exported="true">
</activity>
+ <service android:name="android.voiceinteraction.service.BasicVoiceInteractionService"
+ android:label="CTS test Basic voice interaction service"
+ android:permission="android.permission.BIND_VOICE_INTERACTION"
+ android:exported="true"
+ android:visibleToInstantApps="true">
+ <meta-data android:name="android.voice_interaction"
+ android:resource="@xml/interaction_service_with_hotword" />
+ <intent-filter>
+ <action android:name="android.service.voice.VoiceInteractionService" />
+ </intent-filter>
+ </service>
+ <service android:name="android.voiceinteraction.service.MainHotwordDetectionService"
+ android:permission="android.permission.BIND_HOTWORD_DETECTION_SERVICE"
+ android:isolatedProcess="true"
+ android:exported="true">
+ </service>
+ <service android:name="android.voiceinteraction.service.MainInteractionService"
+ android:label="CTS test voice interaction service"
+ android:permission="android.permission.BIND_VOICE_INTERACTION"
+ android:exported="true"
+ android:visibleToInstantApps="true">
+ <meta-data android:name="android.voice_interaction"
+ android:resource="@xml/interaction_service" />
+ <intent-filter>
+ <action android:name="android.service.voice.VoiceInteractionService" />
+ </intent-filter>
+ </service>
+ <service android:name="android.voiceinteraction.service.MainInteractionSessionService"
+ android:permission="android.permission.BIND_VOICE_INTERACTION"
+ android:process=":session"
+ android:exported="true"
+ android:visibleToInstantApps="true">
+ </service>
+ <service android:name="android.voiceinteraction.service.MainRecognitionService"
+ android:label="CTS Voice Recognition Service"
+ android:exported="true"
+ android:visibleToInstantApps="true">
+ <intent-filter>
+ <action android:name="android.speech.RecognitionService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="android.speech"
+ android:resource="@xml/recognition_service" />
+ </service>
<receiver android:name="VoiceInteractionTestReceiver"
android:exported="true"/>
</application>
diff --git a/tests/tests/voiceinteraction/TEST_MAPPING b/tests/tests/voiceinteraction/TEST_MAPPING
new file mode 100644
index 0000000..50cc659
--- /dev/null
+++ b/tests/tests/voiceinteraction/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsVoiceInteractionTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
index 346ecf7..dd60884 100644
--- a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
+++ b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
@@ -55,9 +55,12 @@
/** Indicate which test event for testing. */
public static final int VOICE_INTERACTION_SERVICE_NORMAL_TEST = 0;
public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_TEST = 1;
+ public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_WITHOUT_PERMISSION_TEST = 2;
public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS = 1;
- public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_FAILURE = 2;
+ public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION = 2;
+ public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION = 3;
+ public static final int HOTWORD_DETECTION_SERVICE_TRIGGER_SHARED_MEMORY_NOT_READ_ONLY = 4;
public static final String TESTCASE_TYPE = "testcase_type";
public static final String TESTINFO = "testinfo";
@@ -128,13 +131,9 @@
}
public static final LocusId DIRECT_ACTIONS_LOCUS_ID = new LocusId("locusId");
- public static final String SERVICE_PACKAGE_NAME = "android.voiceinteraction.service";
public static final String SERVICE_NAME =
"android.voiceinteraction.service/.MainInteractionService";
- public static final String SERVICE_PACKAGE_CTS_VOICE_INTERACTION_MAIN_ACTIVITY_NAME =
- "android.voiceinteraction.service.CtsVoiceInteractionMainActivity";
-
public static final String BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT =
"android.intent.action.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT";
public static final String KEY_SERVICE_TYPE = "serviceType";
diff --git a/tests/tests/voiceinteraction/res/xml/interaction_service_with_hotword.xml b/tests/tests/voiceinteraction/res/xml/interaction_service_with_hotword.xml
new file mode 100644
index 0000000..f9732af
--- /dev/null
+++ b/tests/tests/voiceinteraction/res/xml/interaction_service_with_hotword.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:sessionService="android.voiceinteraction.service.MainInteractionSessionService"
+ android:recognitionService="android.voiceinteraction.service.MainRecognitionService"
+ android:hotwordDetectionService="android.voiceinteraction.service.MainHotwordDetectionService"
+ android:settingsActivity=""
+ android:supportsAssist="false"
+ android:supportsLocalInteraction="true" />
diff --git a/tests/tests/voiceinteraction/res/xml/recognition_service.xml b/tests/tests/voiceinteraction/res/xml/recognition_service.xml
new file mode 100644
index 0000000..8849a80
--- /dev/null
+++ b/tests/tests/voiceinteraction/res/xml/recognition_service.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+
+<recognition-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="" />
diff --git a/tests/tests/voiceinteraction/service/AndroidManifest.xml b/tests/tests/voiceinteraction/service/AndroidManifest.xml
index 4419ea0..e47d30b 100644
--- a/tests/tests/voiceinteraction/service/AndroidManifest.xml
+++ b/tests/tests/voiceinteraction/service/AndroidManifest.xml
@@ -65,14 +65,6 @@
</intent-filter>
<meta-data android:name="android.speech" android:resource="@xml/recognition_service" />
</service>
- <activity android:name=".CtsVoiceInteractionMainActivity"
- android:exported="true"
- android:visibleToInstantApps="true">
- <intent-filter>
- <action android:name="android.intent.action.START_TEST_VOICE_INTERACTION" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
<service android:name=".BasicVoiceInteractionService"
android:label="CTS test Basic voice interaction service"
android:permission="android.permission.BIND_VOICE_INTERACTION"
@@ -87,7 +79,7 @@
</service>
<service android:name=".MainHotwordDetectionService"
android:permission="android.permission.BIND_HOTWORD_DETECTION_SERVICE"
- android:process=":remote"
+ android:isolatedProcess="true"
android:exported="true">
</service>
</application>
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
index fd88975..29b35b7 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
@@ -16,8 +16,10 @@
package android.voiceinteraction.service;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
import android.content.Intent;
-import android.os.Bundle;
+import android.os.PersistableBundle;
import android.os.SharedMemory;
import android.service.voice.AlwaysOnHotwordDetector;
import android.service.voice.VoiceInteractionService;
@@ -35,6 +37,8 @@
// TODO: (b/182236586) Refactor the voice interaction service logic
static final String TAG = "BasicVoiceInteractionService";
+ public static String KEY_FAKE_DATA = "fakeData";
+ public static String VALUE_FAKE_DATA = "fakeData";
public static byte[] FAKE_BYTE_ARRAY_DATA = new byte[] {1, 2, 3};
private boolean mReady = false;
@@ -57,6 +61,10 @@
final int testEvent = intent.getIntExtra(Utils.KEY_TEST_EVENT, -1);
if (testEvent == Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST) {
+ runWithShellPermissionIdentity(() -> {
+ callCreateAlwaysOnHotwordDetector();
+ });
+ } else if (testEvent == Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_WITHOUT_PERMISSION_TEST) {
callCreateAlwaysOnHotwordDetector();
}
@@ -68,7 +76,7 @@
try {
createAlwaysOnHotwordDetector(/* keyphrase */ "Hello Google",
Locale.forLanguageTag("en-US"),
- createFakeBundleData(),
+ createFakePersistableBundleData(),
createFakeSharedMemoryData(),
new AlwaysOnHotwordDetector.Callback() {
@Override
@@ -100,7 +108,12 @@
Log.w(TAG, "callCreateAlwaysOnHotwordDetector() exception: " + e);
broadcastIntentWithResult(
Utils.BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_FAILURE);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION);
+ } catch (SecurityException e) {
+ Log.w(TAG, "callCreateAlwaysOnHotwordDetector() exception: " + e);
+ broadcastIntentWithResult(
+ Utils.BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION);
}
}
@@ -124,10 +137,10 @@
}
}
- private Bundle createFakeBundleData() {
+ private PersistableBundle createFakePersistableBundleData() {
// TODO : Add more data for testing
- Bundle bundle = new Bundle();
- bundle.putByteArray("fakeData", FAKE_BYTE_ARRAY_DATA);
- return bundle;
+ PersistableBundle persistableBundle = new PersistableBundle();
+ persistableBundle.putString(KEY_FAKE_DATA, VALUE_FAKE_DATA);
+ return persistableBundle;
}
}
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/CtsVoiceInteractionMainActivity.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/CtsVoiceInteractionMainActivity.java
deleted file mode 100644
index c7c21e4..0000000
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/CtsVoiceInteractionMainActivity.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.voiceinteraction.service;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.voiceinteraction.common.Utils;
-
-/**
- * Uses this activity to trigger different services for testing hotword detection service related
- * functionality.
- *
- * It will trigger the voice interaction service by the service type and also bring the test event
- * into it.
- */
-public class CtsVoiceInteractionMainActivity extends Activity {
- static final String TAG = "CtsVoiceInteractionMainActivity";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Intent intent = getIntent();
- int serviceType = intent.getIntExtra(Utils.KEY_SERVICE_TYPE, -1);
- Intent serviceIntent = new Intent();
- if (serviceType == Utils.HOTWORD_DETECTION_SERVICE_NONE) {
- serviceIntent.setComponent(new ComponentName(this, MainInteractionService.class));
- } else if (serviceType == Utils.HOTWORD_DETECTION_SERVICE_BASIC) {
- serviceIntent.setComponent(new ComponentName(this,
- BasicVoiceInteractionService.class));
- } else {
- Log.w(TAG, "Never here");
- finish();
- return;
- }
- serviceIntent.putExtra(Utils.KEY_TEST_EVENT, intent.getIntExtra(Utils.KEY_TEST_EVENT, -1));
- ComponentName serviceName = startService(serviceIntent);
- Log.i(TAG, "Started service: " + serviceName);
- finish();
- }
-}
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainHotwordDetectionService.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainHotwordDetectionService.java
index 48cecf0..9bfe321 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainHotwordDetectionService.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainHotwordDetectionService.java
@@ -16,15 +16,14 @@
package android.voiceinteraction.service;
-import android.content.Intent;
import android.media.AudioFormat;
-import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
import android.os.SharedMemory;
import android.service.voice.HotwordDetectionService;
import android.system.ErrnoException;
+import android.text.TextUtils;
import android.util.Log;
-import android.voiceinteraction.common.Utils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -47,19 +46,22 @@
}
@Override
- public void onUpdateState(@Nullable Bundle options, @Nullable SharedMemory sharedMemory) {
+ public void onUpdateState(@Nullable PersistableBundle options,
+ @Nullable SharedMemory sharedMemory) {
Log.d(TAG, "onUpdateState");
- // TODO : Check the options data and sharedMemory data. It will also need to use the new
- // mechanism instead of sendBroadcast to respond the test result when submitting isolated
- // process patch.
+ if (options != null) {
+ String fakeData = options.getString(BasicVoiceInteractionService.KEY_FAKE_DATA);
+ if (!TextUtils.equals(fakeData, BasicVoiceInteractionService.VALUE_FAKE_DATA)) {
+ Log.d(TAG, "options : data is not the same");
+ return;
+ }
+ }
if (sharedMemory != null) {
try {
sharedMemory.mapReadWrite();
- broadcastIntentWithResult(
- Utils.BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_FAILURE);
+ Log.d(TAG, "sharedMemory : is not read-only");
return;
} catch (ErrnoException e) {
// For read-only case
@@ -67,15 +69,7 @@
sharedMemory.close();
}
}
- broadcastIntentWithResult(Utils.BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
- }
-
- private void broadcastIntentWithResult(String intentName, int result) {
- Intent intent = new Intent(intentName)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY)
- .putExtra(Utils.KEY_TEST_RESULT, result);
- Log.d(TAG, "broadcast intent = " + intent + ", result = " + result);
- sendBroadcast(intent);
+ // Report success
+ Log.d(TAG, "onUpdateState success");
}
}
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionService.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionService.java
index 3c2b360..2165854 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionService.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/MainInteractionService.java
@@ -16,6 +16,8 @@
package android.voiceinteraction.service;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
@@ -56,7 +58,9 @@
if (testEvent == Utils.VOICE_INTERACTION_SERVICE_NORMAL_TEST) {
maybeStart();
} else if (testEvent == Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST) {
- callCreateAlwaysOnHotwordDetector();
+ runWithShellPermissionIdentity(() -> {
+ callCreateAlwaysOnHotwordDetector();
+ });
}
return START_NOT_STICKY;
}
@@ -85,7 +89,7 @@
" it is not set as the current voice interaction service");
}
} else {
- showSession(args, VoiceInteractionSession.SHOW_WITH_ASSIST);
+ showSession(args, 0);
}
}
@@ -130,7 +134,7 @@
Log.w(TAG, "callCreateAlwaysOnHotwordDetector() exception: " + e);
broadcastIntentWithResult(
Utils.BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_FAILURE);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION);
}
}
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionBasicTestCase.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionBasicTestCase.java
index d46a290..c787072 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionBasicTestCase.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionBasicTestCase.java
@@ -23,13 +23,13 @@
import android.content.Context;
import android.provider.Settings;
-import com.android.compatibility.common.util.RequiredFeatureRule;
import com.android.compatibility.common.util.SettingsStateChangerRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.runner.RunWith;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
/**
@@ -39,18 +39,17 @@
abstract class AbstractVoiceInteractionBasicTestCase {
// TODO: (b/181943521) Combine the duplicated test class
- protected static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
+ protected static final int TIMEOUT_MS = 5 * 1000;
protected final Context mContext = getInstrumentation().getTargetContext();
@Rule
- public final RequiredFeatureRule mRequiredFeatureRule = new RequiredFeatureRule(
- FEATURE_VOICE_RECOGNIZERS);
+ public final SettingsStateChangerRule mServiceSetterRule = new SettingsStateChangerRule(
+ mContext, Settings.Secure.VOICE_INTERACTION_SERVICE, getVoiceInteractionService());
@Rule
- public final SettingsStateChangerRule mServiceSetterRule = new SettingsStateChangerRule(
- mContext, Settings.Secure.VOICE_INTERACTION_SERVICE,
- "android.voiceinteraction.service/.BasicVoiceInteractionService");
+ public final ActivityScenarioRule<TestVoiceInteractionServiceActivity> mActivityTestRule =
+ new ActivityScenarioRule<>(TestVoiceInteractionServiceActivity.class);
@Before
public void prepareDevice() throws Exception {
@@ -60,4 +59,6 @@
// Dismiss keyguard, in case it's set as "Swipe to unlock".
runShellCommand("wm dismiss-keyguard");
}
+
+ public abstract String getVoiceInteractionService();
}
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionTestCase.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionTestCase.java
index a5cb7d4..721c372 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionTestCase.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/AbstractVoiceInteractionTestCase.java
@@ -24,7 +24,6 @@
import android.provider.Settings;
import android.voiceinteraction.common.Utils;
-import com.android.compatibility.common.util.RequiredFeatureRule;
import com.android.compatibility.common.util.SettingsStateChangerRule;
import org.junit.Before;
@@ -39,16 +38,9 @@
@RunWith(AndroidJUnit4.class)
abstract class AbstractVoiceInteractionTestCase {
- // TODO: use PackageManager's / make it @TestApi
- protected static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
-
protected final Context mContext = getInstrumentation().getTargetContext();
@Rule
- public final RequiredFeatureRule mRequiredFeatureRule = new RequiredFeatureRule(
- FEATURE_VOICE_RECOGNIZERS);
-
- @Rule
public final SettingsStateChangerRule mServiceSetterRule = new SettingsStateChangerRule(
mContext, Settings.Secure.VOICE_INTERACTION_SERVICE, Utils.SERVICE_NAME);
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
index b8bf2ef..0b11257 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
@@ -22,12 +22,11 @@
import android.platform.test.annotations.AppModeFull;
import android.voiceinteraction.common.Utils;
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.compatibility.common.util.BlockingBroadcastReceiver;
-import org.junit.Rule;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,13 +39,8 @@
extends AbstractVoiceInteractionBasicTestCase {
static final String TAG = "HotwordDetectionServiceBasicTest";
- private static final int TIMEOUT_MS = 5 * 1000;
-
- @Rule
- public final ActivityScenarioRule<TestVoiceInteractionServiceActivity> mActivityTestRule =
- new ActivityScenarioRule<>(TestVoiceInteractionServiceActivity.class);
-
@Test
+ @Ignore
public void testHotwordDetectionService_validHotwordDetectionComponentName_triggerSuccess()
throws Throwable {
final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
@@ -54,7 +48,9 @@
receiver.register();
mActivityTestRule.getScenario().onActivity(activity -> {
- activity.triggerHotwordDetectionServiceTest(Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+ activity.triggerHotwordDetectionServiceTest(
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST);
});
final Intent intent = receiver.awaitForBroadcast(TIMEOUT_MS);
@@ -64,4 +60,31 @@
receiver.unregisterQuietly();
}
+
+ @Test
+ public void testHotwordDetectionService_withoutAllowTriggerPermission_triggerFailure()
+ throws Throwable {
+ final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
+ Utils.BROADCAST_HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT);
+ receiver.register();
+
+ mActivityTestRule.getScenario().onActivity(activity -> {
+ activity.triggerHotwordDetectionServiceTest(
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_WITHOUT_PERMISSION_TEST);
+ });
+
+ final Intent intent = receiver.awaitForBroadcast(TIMEOUT_MS);
+ assertThat(intent).isNotNull();
+ assertThat(intent.getIntExtra(Utils.KEY_TEST_RESULT, -1)).isEqualTo(
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION);
+
+ receiver.unregisterQuietly();
+ }
+
+ @Override
+ public String getVoiceInteractionService() {
+ return "android.voiceinteraction.cts/"
+ + "android.voiceinteraction.service.BasicVoiceInteractionService";
+ }
}
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceNonExistenceTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceNonExistenceTest.java
index a01b772..cc161e1 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceNonExistenceTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceNonExistenceTest.java
@@ -22,12 +22,10 @@
import android.platform.test.annotations.AppModeFull;
import android.voiceinteraction.common.Utils;
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.compatibility.common.util.BlockingBroadcastReceiver;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,15 +35,9 @@
@RunWith(AndroidJUnit4.class)
@AppModeFull(reason = "No real use case for instant mode hotword detection service")
public final class HotwordDetectionServiceNonExistenceTest
- extends AbstractVoiceInteractionTestCase {
+ extends AbstractVoiceInteractionBasicTestCase {
static final String TAG = "HotwordDetectionServiceNonExistenceTest";
- private static final int TIMEOUT_MS = 5 * 1000;
-
- @Rule
- public final ActivityScenarioRule<TestVoiceInteractionServiceActivity> mActivityTestRule =
- new ActivityScenarioRule<>(TestVoiceInteractionServiceActivity.class);
-
@Test
public void testHotwordDetectionService_noHotwordDetectionComponentName_triggerFailure()
throws Throwable {
@@ -54,14 +46,22 @@
receiver.register();
mActivityTestRule.getScenario().onActivity(activity -> {
- activity.triggerHotwordDetectionServiceTest(Utils.HOTWORD_DETECTION_SERVICE_NONE);
+ activity.triggerHotwordDetectionServiceTest(
+ Utils.HOTWORD_DETECTION_SERVICE_NONE,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST);
});
final Intent intent = receiver.awaitForBroadcast(TIMEOUT_MS);
assertThat(intent).isNotNull();
assertThat(intent.getIntExtra(Utils.KEY_TEST_RESULT, -1)).isEqualTo(
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_FAILURE);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION);
receiver.unregisterQuietly();
}
+
+ @Override
+ public String getVoiceInteractionService() {
+ return "android.voiceinteraction.cts/"
+ + "android.voiceinteraction.service.MainInteractionService";
+ }
}
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
index 3893f90..70200b8 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
@@ -21,18 +21,27 @@
import android.content.Intent;
import android.util.Log;
import android.voiceinteraction.common.Utils;
+import android.voiceinteraction.service.BasicVoiceInteractionService;
+import android.voiceinteraction.service.MainInteractionService;
public class TestVoiceInteractionServiceActivity extends Activity {
static final String TAG = "TestVoiceInteractionServiceActivity";
- void triggerHotwordDetectionServiceTest(int serviceType) {
- Intent intent = new Intent();
- intent.setAction("android.intent.action.START_TEST_VOICE_INTERACTION");
- intent.setComponent(new ComponentName(Utils.SERVICE_PACKAGE_NAME,
- Utils.SERVICE_PACKAGE_CTS_VOICE_INTERACTION_MAIN_ACTIVITY_NAME));
- intent.putExtra(Utils.KEY_SERVICE_TYPE, serviceType);
- intent.putExtra(Utils.KEY_TEST_EVENT, Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST);
- Log.i(TAG, "triggerHotwordDetectionServiceTest: " + intent);
- startActivity(intent);
+ void triggerHotwordDetectionServiceTest(int serviceType, int testEvent) {
+ Intent serviceIntent = new Intent();
+ if (serviceType == Utils.HOTWORD_DETECTION_SERVICE_NONE) {
+ serviceIntent.setComponent(new ComponentName(this,
+ "android.voiceinteraction.service.MainInteractionService"));
+ } else if (serviceType == Utils.HOTWORD_DETECTION_SERVICE_BASIC) {
+ serviceIntent.setComponent(new ComponentName(this,
+ "android.voiceinteraction.service.BasicVoiceInteractionService"));
+ } else {
+ Log.w(TAG, "Never here");
+ finish();
+ return;
+ }
+ serviceIntent.putExtra(Utils.KEY_TEST_EVENT, testEvent);
+ ComponentName serviceName = startService(serviceIntent);
+ Log.i(TAG, "triggerHotwordDetectionServiceTest Started service: " + serviceName);
}
}
diff --git a/tests/tests/webkit/assets/webkit/page_with_link.html b/tests/tests/webkit/assets/webkit/page_with_link.html
index 50fb78a..4fbe869 100644
--- a/tests/tests/webkit/assets/webkit/page_with_link.html
+++ b/tests/tests/webkit/assets/webkit/page_with_link.html
@@ -15,6 +15,8 @@
<html>
<body>
- <a href="http://foo.com" id="link">a link</a>
+ <!-- This is a relative link to an arbitrary page also hosted on this
+ server. -->
+ <a href="/assets/webkit/test_blankPage.html" id="link">a link</a>
</body>
</html>
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java b/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
index ac0283c..360895a 100644
--- a/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
+++ b/tests/tests/webkit/src/android/webkit/cts/TestHtmlConstants.java
@@ -57,7 +57,11 @@
public static final String LOGIN_FORM_URL = "webkit/test_loginForm.html";
- public static final String EXT_WEB_URL1 = "http://www.example.com/";
+ // Note: tests should avoid loading external URLs if at all possible, since any changes to that
+ // public site (even if it doesn't currently exist) can affect test behavior. The ".test" TLD is
+ // OK because (1) it's reserved for testing by RFC2606 and (2) the test never navigates to this
+ // page.
+ public static final String EXT_WEB_URL1 = "http://www.example.test/";
public static final String PARAM_ASSET_URL = "webkit/test_queryparam.html";
public static final String ANCHOR_ASSET_URL = "webkit/test_anchor.html";
@@ -66,8 +70,9 @@
public static final String DATABASE_ACCESS_URL = "webkit/test_databaseaccess.html";
public static final String STOP_LOADING_URL = "webkit/test_stop_loading.html";
public static final String BLANK_TAG_URL = "webkit/blank_tag.html";
+ // A page with a link to an arbitrary page controlled by the test server (in this case,
+ // BLANK_PAGE_URL).
public static final String PAGE_WITH_LINK_URL = "webkit/page_with_link.html";
- public static final String URL_IN_PAGE_WITH_LINK = "http://foo.com/";
// Not a real page, just triggers a 404 response.
public static final String NON_EXISTENT_PAGE_URL = "webkit/generate_404.html";
public static final String BAD_IMAGE_PAGE_URL = "webkit/test_bad_image_url.html";
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
index ceecde6..47153af 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
@@ -207,8 +207,10 @@
}
}.run();
assertEquals(mainCallCount, mainWebViewClient.getShouldOverrideUrlLoadingCallCount());
- assertEquals(
- TestHtmlConstants.URL_IN_PAGE_WITH_LINK, childWebViewClient.getLastShouldOverrideUrl());
+ // PAGE_WITH_LINK_URL has a link to BLANK_PAGE_URL (an arbitrary page also controlled by the
+ // test server)
+ assertEquals(mWebServer.getAssetUrl(TestHtmlConstants.BLANK_PAGE_URL),
+ childWebViewClient.getLastShouldOverrideUrl());
}
private void clickOnLinkUsingJs(final String linkId, WebViewOnUiThread webViewOnUiThread) {
diff --git a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
index ec3856c..a93d864 100644
--- a/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/HorizontalScrollViewTest.java
@@ -27,6 +27,7 @@
import android.app.Activity;
import android.app.Instrumentation;
+import android.app.compat.CompatChanges;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
@@ -65,6 +66,9 @@
@MediumTest
@RunWith(AndroidJUnit4.class)
public class HorizontalScrollViewTest {
+ static final long USE_STRETCH_EDGE_EFFECT_BY_DEFAULT = 171228096L;
+ static final long USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED = 178807038L;
+
private static final int ITEM_WIDTH = 250;
private static final int ITEM_HEIGHT = 100;
private static final int ITEM_COUNT = 15;
@@ -820,8 +824,11 @@
@Test
public void testEdgeEffectType() {
- // Should default to "glow"
- assertEquals(EdgeEffect.TYPE_GLOW, mScrollViewRegular.getEdgeEffectType());
+ int expectedStartType = (CompatChanges.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_BY_DEFAULT)
+ || CompatChanges.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED))
+ ? EdgeEffect.TYPE_STRETCH : EdgeEffect.TYPE_GLOW;
+ // Should default value
+ assertEquals(expectedStartType, mScrollViewRegular.getEdgeEffectType());
// This one has "stretch" attribute
assertEquals(EdgeEffect.TYPE_STRETCH, mScrollViewStretch.getEdgeEffectType());
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsRecyclingTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsRecyclingTest.java
index 17393cb..9898094 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsRecyclingTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsRecyclingTest.java
@@ -95,7 +95,6 @@
View text2 = container.getChildAt(1);
View text3 = container.getChildAt(2);
-
reapplyRemoteViews(rv, async);
container = mResult.findViewById(R.id.remoteViews_recycle_container);
@@ -311,6 +310,33 @@
doesntRecycleWhenLayoutDoesntMatch(true /* async */);
}
+ private void doesntRecycleWhenViewIdDoesntMatch(boolean async) throws Throwable {
+ RemoteViews rv = createRemoteViews(R.layout.remoteviews_recycle);
+ rv.removeAllViews(R.id.remoteViews_recycle_container);
+ RemoteViews childView = createRemoteViews(R.layout.remoteviews_textview);
+ childView.setViewId(2345);
+ rv.addStableView(R.id.remoteViews_recycle_container, childView, FIRST_TEXT_ID);
+ applyRemoteViews(rv);
+ ViewGroup container = mResult.findViewById(R.id.remoteViews_recycle_container);
+ View text = container.getChildAt(0);
+
+ childView.setViewId(3456);
+ reapplyRemoteViews(rv, async);
+
+ container = mResult.findViewById(R.id.remoteViews_recycle_container);
+ assertNotSame("TextViews", text, container.getChildAt(0));
+ }
+
+ @Test
+ public void doesntRecycleWhenViewIdDoesntMatchSync() throws Throwable {
+ doesntRecycleWhenViewIdDoesntMatch(false /* async */);
+ }
+
+ @Test
+ public void doesntRecycleWhenViewIdDoesntMatchAsync() throws Throwable {
+ doesntRecycleWhenViewIdDoesntMatch(true /* async */);
+ }
+
private void recycleWhenRemovingFromEndAndInsertInMiddleAtManyLevels(boolean async)
throws Throwable {
RemoteViews rv = createRemoteViews(R.layout.remoteviews_recycle);
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
index 4e99196..a08e6ae 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
@@ -1525,6 +1525,33 @@
mRemoteViews.setViewId(100);
}
+ @Test
+ public void testCanRecycleView() throws Throwable {
+ mRemoteViews = new RemoteViews(PACKAGE_NAME, R.layout.remoteviews_textview);
+ mRemoteViews.setViewId(2);
+
+ mActivityRule.runOnUiThread(() -> {
+ mResult = mRemoteViews.apply(mContext, null);
+ });
+
+ mRemoteViews.setViewId(3);
+ assertFalse(mRemoteViews.canRecycleView(mResult));
+
+ mRemoteViews.setViewId(View.NO_ID);
+ assertFalse(mRemoteViews.canRecycleView(mResult));
+
+ mRemoteViews = new RemoteViews(PACKAGE_NAME, R.layout.remoteviews_textview);
+ mRemoteViews.setViewId(2);
+ assertTrue(mRemoteViews.canRecycleView(mResult));
+
+ mRemoteViews = new RemoteViews(PACKAGE_NAME, R.layout.listview_layout);
+ mRemoteViews.setViewId(2);
+ assertFalse(mRemoteViews.canRecycleView(mResult));
+
+ assertFalse(mRemoteViews.canRecycleView(null));
+ assertFalse(mRemoteViews.canRecycleView(new View(mContext)));
+ }
+
private void createSampleImage(File imagefile, int resid) throws IOException {
try (InputStream source = mContext.getResources().openRawResource(resid);
OutputStream target = new FileOutputStream(imagefile)) {
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsThemeColorsTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsThemeColorsTest.java
index f26a7c7..ead1ca3 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsThemeColorsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsThemeColorsTest.java
@@ -146,7 +146,7 @@
// Add our host view to the activity behind this test. This is similar to how launchers
// add widgets to the on-screen UI.
- ViewGroup root = (ViewGroup) mActivityRule.getActivity().findViewById(R.id.remoteView_host);
+ ViewGroup root = mActivityRule.getActivity().findViewById(R.id.remoteView_host);
FrameLayout.MarginLayoutParams lp = new FrameLayout.MarginLayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
@@ -158,8 +158,8 @@
private static List<Integer> generateColorList() {
List<Integer> colors = new ArrayList<>();
- for (int color = android.R.color.system_primary_0;
- color <= android.R.color.system_neutral_1000; color++) {
+ for (int color = android.R.color.system_neutral1_0;
+ color <= android.R.color.system_accent3_1000; color++) {
colors.add(color);
}
return colors;
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java b/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
index 3140f50..8c03aa3 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewOnReceiveContentTest.java
@@ -45,6 +45,7 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.text.Editable;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -599,6 +600,29 @@
@UiThreadTest
@Test
+ public void testDragAndDrop_customReceiver_nonEditableTextView() throws Exception {
+ // Initialize the view and assert preconditions.
+ mTextView.setText("Hello");
+ assertTextAndSelection("Hello", -1, -1);
+ assertThat(mTextView.isTextSelectable()).isFalse();
+ assertThat(mTextView.getText()).isNotInstanceOf(Editable.class);
+
+ // Configure the listener.
+ String[] receiverMimeTypes = new String[] {"text/*"};
+ mTextView.setOnReceiveContentListener(receiverMimeTypes, mMockReceiver);
+
+ // Trigger drop event and assert that the custom receiver was executed.
+ ClipData clip = ClipData.newPlainText("test", "y");
+ triggerDropEvent(clip);
+ verify(mMockReceiver, times(1)).onReceiveContent(
+ eq(mTextView), contentEq(clip, SOURCE_DRAG_AND_DROP, 0));
+ verifyNoMoreInteractions(mMockReceiver);
+ // Note: The cursor/selection should not change since the view is not editable.
+ assertTextAndSelection("Hello", -1, -1);
+ }
+
+ @UiThreadTest
+ @Test
public void testDragAndDrop_customReceiver_unsupportedMimeType() throws Exception {
initTextViewForEditing("xz", 2);
String[] receiverMimeTypes = new String[] {"text/*"};
diff --git a/tests/tests/widget/src/android/widget/cts/ToastTest.java b/tests/tests/widget/src/android/widget/cts/ToastTest.java
index cc44b20..5fbd5c0 100644
--- a/tests/tests/widget/src/android/widget/cts/ToastTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ToastTest.java
@@ -979,6 +979,31 @@
assertCustomToastNotShown(toasts.get(1));
}
+ @Test
+ public void testAppWithUnlimitedToastsPermissionCanPostUnlimitedToasts() throws Throwable {
+ // enable rate limiting to test it
+ SystemUtil.runWithShellPermissionIdentity(() -> mNotificationManager
+ .setToastRateLimitingEnabled(true));
+
+ int highestToastRateLimit = TOAST_RATE_LIMITS[TOAST_RATE_LIMITS.length - 1];
+ List<TextToastInfo> toasts = createTextToasts(highestToastRateLimit + 1, "Text",
+ Toast.LENGTH_SHORT);
+
+ // We have to show one by one to avoid max number of toasts enqueued by a single package at
+ // a time.
+ for (TextToastInfo t : toasts) {
+ // The shell has the android.permission.UNLIMITED_TOASTS permission.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ try {
+ showToast(t);
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ });
+ assertTextToastShownAndHidden(t);
+ }
+ }
+
/** Create given number of text toasts with the same given text and length. */
private List<TextToastInfo> createTextToasts(int num, String text, int length)
throws Throwable {
@@ -986,7 +1011,7 @@
mActivityRule.runOnUiThread(() -> {
toasts.addAll(Stream
.generate(() -> TextToastInfo.create(mContext, text, length))
- .limit(num + 1)
+ .limit(num)
.collect(toList()));
});
return toasts;
@@ -999,7 +1024,7 @@
mActivityRule.runOnUiThread(() -> {
toasts.addAll(Stream
.generate(() -> CustomToastInfo.create(mContext, text, length))
- .limit(num + 1)
+ .limit(num)
.collect(toList()));
});
return toasts;
@@ -1013,6 +1038,12 @@
});
}
+ private void showToast(ToastInfo toast) throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ toast.getToast().show();
+ });
+ }
+
private void assertTextToastsShownAndHidden(List<TextToastInfo> toasts) {
for (int i = 0; i < toasts.size(); i++) {
assertTextToastShownAndHidden(toasts.get(i));
diff --git a/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt b/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
index db31cf8..2837098 100644
--- a/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
+++ b/tests/tests/widget/src/android/widget/cts/util/StretchEdgeUtil.kt
@@ -208,9 +208,10 @@
val downInjected = AnimationUtils.currentAnimationTimeMillis()
- // The receding time is 600 ms, but we don't want to be near the final
- // part of the animation when the pixels may overlap.
- if (downInjected - lastMotion < 400) {
+ // The receding time is based on the spring, but 100 ms should be soon
+ // enough that the animation is within the beginning and it shouldn't have
+ // receded far yet.
+ if (downInjected - lastMotion < 100) {
// Now make sure that we wait until the release should normally have finished:
sleepAnimationTime(600)
@@ -250,11 +251,11 @@
0,
0,
0,
- 50
+ 300
)
// The blue should stretch beyond its normal dimensions
- return bitmap.getPixel(45, 48) == Color.BLUE
+ return bitmap.getPixel(45, 51) == Color.BLUE
}
/**
@@ -270,12 +271,12 @@
view,
0,
0,
- 50,
+ 300,
0
)
// The blue should stretch beyond its normal dimensions
- return bitmap.getPixel(52, 45) == Color.BLUE
+ return bitmap.getPixel(50, 45) == Color.BLUE
}
/**
@@ -292,11 +293,11 @@
0,
89,
0,
- -50
+ -300
)
// The magenta should stretch beyond its normal dimensions
- return bitmap.getPixel(45, 42) == Color.MAGENTA
+ return bitmap.getPixel(45, 39) == Color.MAGENTA
}
/**
@@ -312,12 +313,12 @@
view,
89,
0,
- -50,
+ -300,
0
)
// The magenta should stretch beyond its normal dimensions
- return bitmap.getPixel(38, 45) == Color.MAGENTA
+ return bitmap.getPixel(39, 45) == Color.MAGENTA
}
/**
@@ -334,11 +335,11 @@
0,
0,
0,
- 89
+ 300
) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
// The blue should stretch beyond its normal dimensions
- return bitmap.getPixel(45, 52) == Color.BLUE
+ return bitmap.getPixel(45, 50) == Color.BLUE
}
/**
@@ -354,12 +355,12 @@
view,
0,
0,
- 89,
+ 300,
0
) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
// The blue should stretch beyond its normal dimensions
- return bitmap.getPixel(52, 45) == Color.BLUE
+ return bitmap.getPixel(50, 45) == Color.BLUE
}
/**
@@ -376,11 +377,11 @@
0,
89,
0,
- -89
+ -300
) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
// The magenta should stretch beyond its normal dimensions
- return bitmap.getPixel(45, 38) == Color.MAGENTA
+ return bitmap.getPixel(45, 39) == Color.MAGENTA
}
/**
@@ -396,10 +397,10 @@
view,
89,
0,
- -89,
+ -300,
0
) ?: return true // when timing fails to get a bitmap, don't treat it as a flake
// The magenta should stretch beyond its normal dimensions
- return bitmap.getPixel(38, 45) == Color.MAGENTA
+ return bitmap.getPixel(39, 45) == Color.MAGENTA
}
diff --git a/tests/tests/wifi/TEST_MAPPING b/tests/tests/wifi/TEST_MAPPING
index e80f848..7ddc308 100644
--- a/tests/tests/wifi/TEST_MAPPING
+++ b/tests/tests/wifi/TEST_MAPPING
@@ -9,8 +9,7 @@
]
}
],
- // TODO(b/179512200): move to mainline-presubmit once passing
- "mainline-postsubmit": [
+ "mainline-presubmit": [
{
"name": "CtsWifiTestCases[com.google.android.wifi.apex]",
"options": [
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/CoexUnsafeChannelTest.java b/tests/tests/wifi/src/android/net/wifi/cts/CoexUnsafeChannelTest.java
new file mode 100644
index 0000000..7c192f2
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/CoexUnsafeChannelTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.cts;
+
+import android.internal.wifi.WifiAnnotations;
+import android.net.wifi.CoexUnsafeChannel;
+import android.net.wifi.WifiScanner;
+import android.test.AndroidTestCase;
+
+public class CoexUnsafeChannelTest extends AndroidTestCase {
+ final static int TEST_BAND = WifiScanner.WIFI_BAND_24_GHZ;
+ final static int TEST_CHANNEL = 6;
+ final static int TEST_POWER_CAP_DBM = -50;
+
+ public void testNoPowerCapConstructor() {
+ CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(TEST_BAND, TEST_CHANNEL);
+
+ assertEquals(unsafeChannel.getBand(), TEST_BAND);
+ assertEquals(unsafeChannel.getChannel(), TEST_CHANNEL);
+ assertFalse(unsafeChannel.isPowerCapAvailable());
+ }
+
+ public void testPowerCapConstructor() {
+ CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(TEST_BAND, TEST_CHANNEL,
+ TEST_POWER_CAP_DBM);
+
+ assertEquals(unsafeChannel.getBand(), TEST_BAND);
+ assertEquals(unsafeChannel.getChannel(), TEST_CHANNEL);
+ assertTrue(unsafeChannel.isPowerCapAvailable());
+ assertEquals(unsafeChannel.getPowerCapDbm(), TEST_POWER_CAP_DBM);
+ }
+
+ public void testSetPowerCap() {
+ CoexUnsafeChannel unsafeChannel = new CoexUnsafeChannel(TEST_BAND, TEST_CHANNEL);
+
+ unsafeChannel.setPowerCapDbm(TEST_POWER_CAP_DBM);
+
+ assertEquals(unsafeChannel.getPowerCapDbm(), TEST_POWER_CAP_DBM);
+ }
+}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
index 8af7090..18733c9 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
@@ -127,7 +127,7 @@
mTestHelper.turnScreenOn();
// Clear any existing app state before each test.
- if (WifiBuildCompat.isAtLeastS(mContext)) {
+ if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
}
@@ -272,6 +272,8 @@
assertThat(statsEntry.getContentionTimeStats(
WME_ACCESS_CATEGORY_VO).getContentionNumSamples()).isAtLeast(0);
assertThat(statsEntry.getChannelUtilizationRatio()).isIn(Range.closed(0, 255));
+ assertThat(statsEntry.getRateStats()).isNotNull();
+ assertThat(statsEntry.getWifiLinkLayerRadioStats()).isNotNull();
}
// no longer populated, return default value.
assertThat(statsEntry.getCellularDataNetworkType())
@@ -522,7 +524,7 @@
connectedNetworkScorer.resetCountDownLatch(countDownLatchScorer);
// Restart wifi subsystem.
- mWifiManager.restartWifiSubsystem(null);
+ mWifiManager.restartWifiSubsystem();
// Wait for the device to connect back.
PollingCheck.check(
"Wifi not connected",
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java b/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
index 749cdaa..6d111fe 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
@@ -18,12 +18,13 @@
import static android.net.wifi.EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_TIMEOUT;
import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_PSK;
-import static android.net.wifi.WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_DEFAULT;
+import static android.net.wifi.WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1;
import static android.net.wifi.WifiManager.EASY_CONNECT_NETWORK_ROLE_STA;
import android.annotation.NonNull;
import android.app.UiAutomation;
import android.content.Context;
+import android.net.Uri;
import android.net.wifi.EasyConnectStatusCallback;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
@@ -117,7 +118,7 @@
}
@Override
- public void onBootstrapUriGenerated(@NonNull String uri) {
+ public void onBootstrapUriGenerated(@NonNull Uri dppUri) {
synchronized (mLock) {
mOnBootstrapUriGeneratedCallback = true;
mLock.notify();
@@ -219,8 +220,10 @@
try {
uiAutomation.adoptShellPermissionIdentity();
synchronized (mLock) {
+ assertTrue(mWifiManager.getEasyConnectMaxAllowedResponderDeviceInfoLength()
+ > TEST_DEVICE_INFO.length());
mWifiManager.startEasyConnectAsEnrolleeResponder(TEST_DEVICE_INFO,
- EASY_CONNECT_CRYPTOGRAPHY_CURVE_DEFAULT, mExecutor,
+ EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mExecutor,
mEasyConnectStatusCallback);
// Wait for supplicant to generate DPP URI and trigger the callback function to
// provide the generated URI.
@@ -255,7 +258,7 @@
try {
uiAutomation.adoptShellPermissionIdentity();
mWifiManager.startEasyConnectAsEnrolleeResponder(TEST_WRONG_DEVICE_INFO,
- EASY_CONNECT_CRYPTOGRAPHY_CURVE_DEFAULT, mExecutor,
+ EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1, mExecutor,
mEasyConnectStatusCallback);
fail("startEasyConnectAsEnrolleeResponder did not throw an IllegalArgumentException"
+ "on passing a wrong device info!");
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
index b90d639..df7cc77 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
@@ -266,6 +266,16 @@
}
}
+ @NonNull
+ private WifiInfo getWifiInfo(@NonNull NetworkCapabilities networkCapabilities) {
+ if (BuildCompat.isAtLeastS()) {
+ // WifiInfo in transport info, only available in S.
+ return (WifiInfo) networkCapabilities.getTransportInfo();
+ } else {
+ return mWifiManager.getConnectionInfo();
+ }
+ }
+
private static void assertConnectionEquals(@NonNull WifiConfiguration network,
@NonNull WifiInfo wifiInfo) {
assertThat(network.SSID).isEqualTo(wifiInfo.getSSID());
@@ -276,7 +286,6 @@
private final CountDownLatch mCountDownLatch;
public boolean onSuccessCalled = false;
public boolean onFailedCalled = false;
- public int failureReason = -1;
TestActionListener(CountDownLatch countDownLatch) {
mCountDownLatch = countDownLatch;
@@ -336,8 +345,12 @@
assertThat(countDownLatchNr.await(
DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(testNetworkCallback.onAvailableCalled).isTrue();
- assertConnectionEquals(
- network, (WifiInfo) testNetworkCallback.networkCapabilities.getTransportInfo());
+ final WifiInfo wifiInfo = getWifiInfo(testNetworkCallback.networkCapabilities);
+ assertConnectionEquals(network, wifiInfo);
+ if (BuildCompat.isAtLeastS()) {
+ // User connections should always be primary.
+ assertThat(wifiInfo.isPrimary()).isTrue();
+ }
} catch (Throwable e /* catch assertions & exceptions */) {
// Unregister the network callback in case of any failure (since we don't end up
// returning the network callback to the caller).
@@ -482,9 +495,18 @@
assertThat(countDownLatch.await(
DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
assertThat(testNetworkCallback.onAvailableCalled).isTrue();
- assertConnectionEquals(
- network,
- (WifiInfo) testNetworkCallback.networkCapabilities.getTransportInfo());
+ final WifiInfo wifiInfo = getWifiInfo(testNetworkCallback.networkCapabilities);
+ assertConnectionEquals(network, wifiInfo);
+ if (BuildCompat.isAtLeastS()) {
+ // If STA concurrency for restricted connection is supported, this should not
+ // be the primary connection.
+ if (!restrictedNetworkCapabilities.isEmpty()
+ && mWifiManager.isStaConcurrencyForRestrictedConnectionsSupported()) {
+ assertThat(wifiInfo.isPrimary()).isFalse();
+ } else {
+ assertThat(wifiInfo.isPrimary()).isTrue();
+ }
+ }
} else {
// now wait for connection to timeout.
assertThat(countDownLatch.await(
@@ -670,15 +692,17 @@
assertThat(testNetworkCallback.onUnavailableCalled).isTrue();
} else {
assertThat(testNetworkCallback.onAvailableCalled).isTrue();
- final WifiInfo wifiInfo;
- if (BuildCompat.isAtLeastS()) {
- // WifiInfo in transport info, only available in S.
- wifiInfo =
- (WifiInfo) testNetworkCallback.networkCapabilities.getTransportInfo();
- } else {
- wifiInfo = mWifiManager.getConnectionInfo();
- }
+ final WifiInfo wifiInfo = getWifiInfo(testNetworkCallback.networkCapabilities);
assertConnectionEquals(network, wifiInfo);
+ if (BuildCompat.isAtLeastS()) {
+ // If STA concurrency for local only connection is supported, this should not
+ // be the primary connection.
+ if (mWifiManager.isStaConcurrencyForLocalOnlyConnectionsSupported()) {
+ assertThat(wifiInfo.isPrimary()).isFalse();
+ } else {
+ assertThat(wifiInfo.isPrimary()).isTrue();
+ }
+ }
}
} catch (Throwable e /* catch assertions & exceptions */) {
try {
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiBuildCompat.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiBuildCompat.java
index abc3d65..0730e37 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiBuildCompat.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiBuildCompat.java
@@ -27,11 +27,12 @@
* Wrapper class for checking the wifi module version.
*
* Wifi CTS tests for a dessert release can be run on older dessert releases as a part of MTS.
- * Since wifi module is optional, not all older dessert release will contain the wifi module from the
- * provided dessert release (which means we cannot use new wifi API's on those devices).
+ * Since wifi module is optional, not all older dessert release will contain the wifi module from
+ * the provided dessert release (which means we cannot use new wifi API's on those devices).
*
* <p>
- * This utility tries to help solve that problem by trying to check if the device is running atleast
+ * This utility tries to help solve that problem by trying to check if the device is running at
+ * least
* <li> The provided dessert release using {@link BuildCompat}, OR</li>
* <li> The wifi module from the provided dessert release on an older dessert release device</li>
*
@@ -41,12 +42,11 @@
*
* Note: This does not check for granular wifi module version codes, only that it is some version
* of the module from the provided dessert release.
- *
*/
public class WifiBuildCompat {
private static final String WIFI_APEX_NAME = "com.android.wifi";
- private static long WIFI_APEX_BASE_VERSION_CODE_FOR_S = 310000000;
+ private static final long WIFI_APEX_BASE_VERSION_CODE_FOR_S = 310000000;
private static long getWifiApexVersionCode(@NonNull Context ctx) {
PackageManager packageManager = ctx.getPackageManager();
@@ -66,7 +66,7 @@
private WifiBuildCompat() { }
- public static boolean isAtLeastS(@NonNull Context ctx) {
+ public static boolean isPlatformOrWifiModuleAtLeastS(@NonNull Context ctx) {
return BuildCompat.isAtLeastS()
|| getWifiApexVersionCode(ctx) >= WIFI_APEX_BASE_VERSION_CODE_FOR_S;
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
index 45f8132..3ea7b7d 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiInfoTest.java
@@ -209,6 +209,10 @@
assertThat(wifiInfo.getRxLinkSpeedMbps()).isAtLeast(-1);
assertThat(wifiInfo.getMaxSupportedTxLinkSpeedMbps()).isAtLeast(-1);
assertThat(wifiInfo.getMaxSupportedRxLinkSpeedMbps()).isAtLeast(-1);
+ if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
+ assertThat(wifiInfo.getCurrentSecurityType()).isNotEqualTo(
+ WifiInfo.SECURITY_TYPE_UNKNOWN);
+ }
}
/**
@@ -255,4 +259,26 @@
assertThat(info2.getRssi()).isEqualTo(TEST_RSSI);
assertThat(info2.getNetworkId()).isEqualTo(TEST_NETWORK_ID2);
}
+
+ /**
+ * Test that setCurrentSecurityType and getCurrentSecurityType work as expected
+ * @throws Exception
+ */
+ public void testWifiInfoCurrentSecurityType() throws Exception {
+ if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
+ return;
+ }
+ WifiInfo.Builder builder = new WifiInfo.Builder()
+ .setSsid(TEST_SSID.getBytes(StandardCharsets.UTF_8))
+ .setBssid(TEST_BSSID)
+ .setRssi(TEST_RSSI)
+ .setNetworkId(TEST_NETWORK_ID);
+
+ WifiInfo info = builder.build();
+ assertEquals(WifiInfo.SECURITY_TYPE_UNKNOWN, info.getCurrentSecurityType());
+
+ builder.setCurrentSecurityType(WifiInfo.SECURITY_TYPE_SAE);
+ info = builder.build();
+ assertEquals(WifiInfo.SECURITY_TYPE_SAE, info.getCurrentSecurityType());
+ }
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
index a4b1b29..b30f898 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -184,7 +184,8 @@
private static final String TEST_PSK_CAP = "[RSN-PSK-CCMP]";
private static final String TEST_BSSID = "00:01:02:03:04:05";
private static final String TEST_COUNTRY_CODE = "JP";
- public static final String TEST_DOM_SUBJECT_MATCH = "domSubjectMatch";
+ private static final String TEST_DOM_SUBJECT_MATCH = "domSubjectMatch";
+ private static final int TEST_SUB_ID = 2;
private IntentFilter mIntentFilter;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -535,7 +536,7 @@
return;
}
try {
- mWifiManager.restartWifiSubsystem("CTS triggered");
+ mWifiManager.restartWifiSubsystem();
fail("The restartWifiSubsystem should not succeed - privileged call");
} catch (SecurityException e) {
// expected
@@ -559,7 +560,7 @@
mWifiManager.registerSubsystemRestartTrackingCallback(mExecutor,
mSubsystemRestartTrackingCallback);
synchronized (mLock) {
- mWifiManager.restartWifiSubsystem("CTS triggered");
+ mWifiManager.restartWifiSubsystem();
mLock.wait(TEST_WAIT_DURATION_MS);
}
assertEquals(mSubsystemRestartStatus, 1); // 1: restarting
@@ -665,8 +666,8 @@
for (int i = 0; i < testData.size(); i++) {
for (int frequency : testData.valueAt(i)) {
- assertEquals(frequency, ScanResult.convertChannelToFrequencyMhz(
- ScanResult.convertFrequencyMhzToChannel(frequency), testData.keyAt(i)));
+ assertEquals(frequency, ScanResult.convertChannelToFrequencyMhzIfSupported(
+ ScanResult.convertFrequencyMhzToChannelIfSupported(frequency), testData.keyAt(i)));
}
}
}
@@ -1115,6 +1116,118 @@
}
/**
+ * Verify that {@link WifiManager#addetworkPrivileged} throws a SecurityException when called
+ * by a normal app.
+ */
+ public void testAddNetworkPrivilegedNotAllowedForNormalApps() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
+ // Skip the test if wifi module version is older than S.
+ return;
+ }
+ try {
+ WifiConfiguration newOpenNetwork = new WifiConfiguration();
+ newOpenNetwork.SSID = "\"" + TEST_SSID_UNQUOTED + "\"";
+ mWifiManager.addNetworkPrivileged(newOpenNetwork);
+ fail("A normal app should not be able to call this API.");
+ } catch (SecurityException e) {
+ }
+ }
+
+ /**
+ * Verify {@link WifiManager#addetworkPrivileged} throws an exception when null is the input.
+ */
+ public void testAddNetworkPrivilegedBadInput() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
+ // Skip the test if wifi module version is older than S.
+ return;
+ }
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ mWifiManager.addNetworkPrivileged(null);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
+ * Verify {@link WifiManager#addetworkPrivileged} returns the proper failure status code
+ * when adding an enterprise config with mandatory fields not filled in.
+ */
+ public void testAddNetworkPrivilegedFailureBadEnterpriseConfig() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
+ // Skip the test if wifi module version is older than S.
+ return;
+ }
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ WifiConfiguration wifiConfiguration = new WifiConfiguration();
+ wifiConfiguration.SSID = SSID1;
+ wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+ wifiConfiguration.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
+ WifiManager.AddNetworkResult result =
+ mWifiManager.addNetworkPrivileged(wifiConfiguration);
+ assertEquals(WifiManager.AddNetworkResult.STATUS_INVALID_CONFIGURATION_ENTERPRISE,
+ result.statusCode);
+ assertEquals(-1, result.networkId);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
+ * Verify {@link WifiManager#addetworkPrivileged} works properly when the calling app has
+ * permissions.
+ */
+ public void testAddNetworkPrivilegedSuccess() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ if (!WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
+ // Skip the test if wifi module version is older than S.
+ return;
+ }
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ WifiConfiguration newOpenNetwork = new WifiConfiguration();
+ newOpenNetwork.SSID = "\"" + TEST_SSID_UNQUOTED + "\"";
+ WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(newOpenNetwork);
+ assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode);
+ assertTrue(result.networkId >= 0);
+ List<WifiConfiguration> configuredNetworks = mWifiManager.getConfiguredNetworks();
+ boolean found = false;
+ for (WifiConfiguration config : configuredNetworks) {
+ if (config.networkId == result.networkId
+ && config.SSID.equals(newOpenNetwork.SSID)) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue("addNetworkPrivileged returns success but the network is not found", found);
+ mWifiManager.removeNetwork(result.networkId);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
* Verify that applications can only have one registered LocalOnlyHotspot request at a time.
*
* Note: Location mode must be enabled for this test.
@@ -2128,7 +2241,7 @@
"SoftAp channel and state mismatch!!!", 5_000,
() -> {
executor.runAll();
- int sapChannel = ScanResult.convertFrequencyMhzToChannel(
+ int sapChannel = ScanResult.convertFrequencyMhzToChannelIfSupported(
callback.getCurrentSoftApInfo().getFrequency());
return WifiManager.WIFI_AP_STATE_ENABLED == callback.getCurrentState()
&& testBandsAndChannels.valueAt(0) == sapChannel;
@@ -2808,6 +2921,40 @@
}
/**
+ * Tests {@link WifiManager#setVerboseLoggingLevel(int)}.
+ */
+ public void testSetVerboseLogging() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ Boolean currState = null;
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ currState = mWifiManager.isVerboseLoggingEnabled();
+
+ mWifiManager.setVerboseLoggingLevel(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED);
+ assertTrue(mWifiManager.isVerboseLoggingEnabled());
+ assertEquals(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED,
+ mWifiManager.getVerboseLoggingLevel());
+
+ mWifiManager.setVerboseLoggingLevel(WifiManager.VERBOSE_LOGGING_LEVEL_DISABLED);
+ assertFalse(mWifiManager.isVerboseLoggingEnabled());
+ assertEquals(WifiManager.VERBOSE_LOGGING_LEVEL_DISABLED,
+ mWifiManager.getVerboseLoggingLevel());
+
+ mWifiManager.setVerboseLoggingLevel(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY);
+ assertTrue(mWifiManager.isVerboseLoggingEnabled());
+ assertEquals(WifiManager.VERBOSE_LOGGING_LEVEL_ENABLED_SHOW_KEY,
+ mWifiManager.getVerboseLoggingLevel());
+ } finally {
+ if (currState != null) mWifiManager.setVerboseLoggingEnabled(currState);
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ /**
* Tests {@link WifiManager#factoryReset()} cannot be invoked from a non-privileged app.
*
* Note: This intentionally does not test the full reset functionality because it causes
@@ -2847,14 +2994,14 @@
}
/**
- * Verify that startTemporarilyDisablingAllNonCarrierMergedWifi disconnects wifi and disables
- * autoconnect to non-carrier-merged networks. Then verify that
- * stopTemporarilyDisablingAllNonCarrierMergedWifi makes the disabled networks clear to connect
+ * Verify that startRestrictingAutoJoinToSubscriptionId disconnects wifi and disables
+ * auto-connect to non-carrier-merged networks. Then verify that
+ * stopRestrictingAutoJoinToSubscriptionId makes the disabled networks clear to connect
* again.
* TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
*/
@SdkSuppress(minSdkVersion = 31, codeName = "S")
- public void testStartAndStopTemporarilyDisablingAllNonCarrierMergedWifi() throws Exception {
+ public void testStartAndStopRestrictingAutoJoinToSubscriptionId() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
@@ -2863,11 +3010,11 @@
waitForConnection();
int fakeSubscriptionId = 5;
ShellIdentityUtils.invokeWithShellPermissions(() ->
- mWifiManager.startTemporarilyDisablingAllNonCarrierMergedWifi(fakeSubscriptionId));
+ mWifiManager.startRestrictingAutoJoinToSubscriptionId(fakeSubscriptionId));
startScan();
ensureNotConnected();
ShellIdentityUtils.invokeWithShellPermissions(() ->
- mWifiManager.stopTemporarilyDisablingAllNonCarrierMergedWifi());
+ mWifiManager.stopRestrictingAutoJoinToSubscriptionId());
startScan();
waitForConnection();
}
@@ -4018,4 +4165,30 @@
}
mWifiManager.isDecoratedIdentitySupported();
}
+
+ /**
+ * Tests {@link WifiManager#setCarrierNetworkOffloadEnabled)} and
+ * {@link WifiManager#isCarrierNetworkOffloadEnabled} work as expected.
+ * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+ */
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
+ public void testSetCarrierNetworkOffloadEnabled() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ assertTrue(mWifiManager.isCarrierNetworkOffloadEnabled(TEST_SUB_ID, false));
+ // The below API only works with privileged permissions (obtained via shell identity
+ // for test)
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ mWifiManager.setCarrierNetworkOffloadEnabled(TEST_SUB_ID, false, false);
+ assertFalse(mWifiManager.isCarrierNetworkOffloadEnabled(TEST_SUB_ID, false));
+ mWifiManager.setCarrierNetworkOffloadEnabled(TEST_SUB_ID, false, true);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ assertTrue(mWifiManager.isCarrierNetworkOffloadEnabled(TEST_SUB_ID, false));
+ }
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
index 6f5b892..02855ec 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
@@ -288,7 +288,7 @@
mConnectivityManager.unregisterNetworkCallback(mNrNetworkCallback);
}
// Clear any existing app state after each test.
- if (WifiBuildCompat.isAtLeastS(mContext)) {
+ if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
ShellIdentityUtils.invokeWithShellPermissions(
() -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
index f06b563..a22cc6a 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
@@ -97,134 +97,134 @@
private static boolean sWasScanThrottleEnabled;
private static boolean sWasWifiEnabled;
- private Context mContext;
- private WifiManager mWifiManager;
- private ConnectivityManager mConnectivityManager;
- private UiDevice mUiDevice;
- private WifiConfiguration mTestNetwork;
- private ConnectivityManager.NetworkCallback mNsNetworkCallback;
+ private static Context sContext;
+ private static WifiManager sWifiManager;
+ private static ConnectivityManager sConnectivityManager;
+ private static UiDevice sUiDevice;
+ private static WifiConfiguration sTestNetwork;
+ private static ConnectivityManager.NetworkCallback sNsNetworkCallback;
+ private static TestHelper sTestHelper;
+
private ScheduledExecutorService mExecutorService;
- private TestHelper mTestHelper;
private static final int DURATION_MILLIS = 10_000;
@BeforeClass
public static void setUpClass() throws Exception {
- Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ sContext = InstrumentationRegistry.getInstrumentation().getContext();
// skip the test if WiFi is not supported
// Don't use assumeTrue in @BeforeClass
- if (!WifiFeature.isWifiSupported(context)) return;
+ if (!WifiFeature.isWifiSupported(sContext)) return;
+ // skip the test if location is not supported
+ if (!sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION)) return;
+ // skip if the location is disabled
+ if (!sContext.getSystemService(LocationManager.class).isLocationEnabled()) return;
- WifiManager wifiManager = context.getSystemService(WifiManager.class);
- assertThat(wifiManager).isNotNull();
+ sWifiManager = sContext.getSystemService(WifiManager.class);
+ assertThat(sWifiManager).isNotNull();
+ sConnectivityManager = sContext.getSystemService(ConnectivityManager.class);
+ sUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ sTestHelper = new TestHelper(sContext, sUiDevice);
// turn on verbose logging for tests
sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
- () -> wifiManager.isVerboseLoggingEnabled());
+ () -> sWifiManager.isVerboseLoggingEnabled());
ShellIdentityUtils.invokeWithShellPermissions(
- () -> wifiManager.setVerboseLoggingEnabled(true));
+ () -> sWifiManager.setVerboseLoggingEnabled(true));
// Disable scan throttling for tests.
sWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
- () -> wifiManager.isScanThrottleEnabled());
+ () -> sWifiManager.isScanThrottleEnabled());
ShellIdentityUtils.invokeWithShellPermissions(
- () -> wifiManager.setScanThrottleEnabled(false));
+ () -> sWifiManager.setScanThrottleEnabled(false));
// enable Wifi
sWasWifiEnabled = ShellIdentityUtils.invokeWithShellPermissions(
- () -> wifiManager.isWifiEnabled());
- if (!wifiManager.isWifiEnabled()) {
- ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
+ () -> sWifiManager.isWifiEnabled());
+ if (!sWifiManager.isWifiEnabled()) {
+ ShellIdentityUtils.invokeWithShellPermissions(() -> sWifiManager.setWifiEnabled(true));
}
- PollingCheck.check("Wifi not enabled", DURATION_MILLIS, () -> wifiManager.isWifiEnabled());
- }
-
- @AfterClass
- public static void tearDownClass() throws Exception {
- Context context = InstrumentationRegistry.getInstrumentation().getContext();
- if (!WifiFeature.isWifiSupported(context)) return;
-
- WifiManager wifiManager = context.getSystemService(WifiManager.class);
- assertThat(wifiManager).isNotNull();
-
- ShellIdentityUtils.invokeWithShellPermissions(
- () -> wifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
- ShellIdentityUtils.invokeWithShellPermissions(
- () -> wifiManager.setVerboseLoggingEnabled(sWasVerboseLoggingEnabled));
- ShellIdentityUtils.invokeWithShellPermissions(
- () -> wifiManager.setWifiEnabled(sWasWifiEnabled));
- }
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getInstrumentation().getContext();
- mWifiManager = mContext.getSystemService(WifiManager.class);
- mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
- mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- mExecutorService = Executors.newSingleThreadScheduledExecutor();
- mTestHelper = new TestHelper(mContext, mUiDevice);
-
- // skip the test if WiFi is not supported
- assumeTrue(WifiFeature.isWifiSupported(mContext));
- // skip the test if location is not supported
- assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION));
-
- assertWithMessage("Please enable location for this test!").that(
- mContext.getSystemService(LocationManager.class).isLocationEnabled()).isTrue();
-
- // turn screen on
- mTestHelper.turnScreenOn();
-
- // Clear any existing app state before each test.
- if (WifiBuildCompat.isAtLeastS(mContext)) {
- ShellIdentityUtils.invokeWithShellPermissions(
- () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
- }
+ PollingCheck.check("Wifi not enabled", DURATION_MILLIS, () -> sWifiManager.isWifiEnabled());
// check we have >= 1 saved network
List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
- () -> mWifiManager.getPrivilegedConfiguredNetworks());
- assertFalse("Need at least one saved network", savedNetworks.isEmpty());
+ () -> sWifiManager.getPrivilegedConfiguredNetworks());
+ if (savedNetworks.isEmpty()) {
+ return;
+ }
// Pick any network in range.
- mTestNetwork = TestHelper.findMatchingSavedNetworksWithBssid(mWifiManager, savedNetworks)
- .get(0);
- // Disconnect & disable auto-join on the saved network to prevent auto-connect from
+ List<WifiConfiguration> networks = TestHelper.findMatchingSavedNetworksWithBssid(
+ sWifiManager, savedNetworks);
+ if (!networks.isEmpty()) {
+ sTestNetwork = networks.get(0);
+ }
+
+ // Disable auto-join on the saved network to prevent auto-connect from
// interfering with the test.
ShellIdentityUtils.invokeWithShellPermissions(
() -> {
for (WifiConfiguration savedNetwork : savedNetworks) {
- mWifiManager.disableNetwork(savedNetwork.networkId);
+ sWifiManager.disableNetwork(savedNetwork.networkId);
}
- mWifiManager.disconnect();
});
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ if (!WifiFeature.isWifiSupported(sContext)) return;
+
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> sWifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> sWifiManager.setVerboseLoggingEnabled(sWasVerboseLoggingEnabled));
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> sWifiManager.setWifiEnabled(sWasWifiEnabled));
+
+ // Re-enable networks.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> {
+ for (WifiConfiguration savedNetwork : sWifiManager.getConfiguredNetworks()) {
+ sWifiManager.enableNetwork(savedNetwork.networkId, false);
+ }
+ });
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mExecutorService = Executors.newSingleThreadScheduledExecutor();
+ // turn screen on
+ sTestHelper.turnScreenOn();
+
+ // Disconnect current network if any.
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> sWifiManager.disconnect());
// Wait for Wifi to be disconnected.
PollingCheck.check(
"Wifi not disconnected",
20_000,
- () -> mWifiManager.getConnectionInfo().getNetworkId() == -1);
+ () -> sWifiManager.getConnectionInfo().getNetworkId() == -1);
+
+ // Clear any existing app state before each test.
+ if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(sContext)) {
+ ShellIdentityUtils.invokeWithShellPermissions(
+ () -> sWifiManager.removeAppState(myUid(), sContext.getPackageName()));
+ }
}
@After
public void tearDown() throws Exception {
- // Re-enable networks.
- ShellIdentityUtils.invokeWithShellPermissions(
- () -> {
- for (WifiConfiguration savedNetwork : mWifiManager.getConfiguredNetworks()) {
- mWifiManager.enableNetwork(savedNetwork.networkId, false);
- }
- });
// Release the requests after the test.
- if (mNsNetworkCallback != null) {
- mConnectivityManager.unregisterNetworkCallback(mNsNetworkCallback);
+ if (sNsNetworkCallback != null) {
+ sConnectivityManager.unregisterNetworkCallback(sNsNetworkCallback);
}
mExecutorService.shutdownNow();
// Clear any existing app state after each test.
- if (BuildCompat.isAtLeastS()) {
+ if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(sContext)) {
ShellIdentityUtils.invokeWithShellPermissions(
- () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+ () -> sWifiManager.removeAppState(myUid(), sContext.getPackageName()));
}
- mTestHelper.turnScreenOff();
+ sTestHelper.turnScreenOff();
}
private static final String CA_SUITE_B_RSA3072_CERT_STRING =
@@ -780,7 +780,7 @@
WifiNetworkSuggestion suggestion =
createBuilderWithCommonParams()
.setWpa3Passphrase(TEST_PASSPHRASE)
- .enableWpa3SaeH2eOnlyMode(true)
+ .setIsWpa3SaeH2eOnlyModeEnabled(true)
.build();
validateCommonParams(suggestion);
assertEquals(TEST_PASSPHRASE, suggestion.getPassphrase());
@@ -1102,12 +1102,13 @@
@SdkSuppress(minSdkVersion = 31, codeName = "S")
@Test
public void testConnectToSuggestion() throws Exception {
+ assertNotNull(sTestNetwork);
WifiNetworkSuggestion suggestion =
TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
- mTestNetwork)
+ sTestNetwork)
.build();
- mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
- mTestNetwork, suggestion, mExecutorService,
+ sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
+ sTestNetwork, suggestion, mExecutorService,
Set.of() /* restrictedNetworkCapability */);
}
@@ -1119,13 +1120,14 @@
@SdkSuppress(minSdkVersion = 31, codeName = "S")
@Test
public void testConnectToOemPaidSuggestion() throws Exception {
+ assertNotNull(sTestNetwork);
WifiNetworkSuggestion suggestion =
TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
- mTestNetwork)
+ sTestNetwork)
.setOemPaid(true)
.build();
- mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
- mTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
+ sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
+ sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
}
/**
@@ -1136,14 +1138,15 @@
@SdkSuppress(minSdkVersion = 31, codeName = "S")
@Test
public void testConnectToOemPaidAndOemPrivateSuggestion() throws Exception {
+ assertNotNull(sTestNetwork);
WifiNetworkSuggestion suggestion =
TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
- mTestNetwork)
+ sTestNetwork)
.setOemPaid(true)
.setOemPrivate(true)
.build();
- mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
- mTestNetwork, suggestion, mExecutorService,
+ sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
+ sTestNetwork, suggestion, mExecutorService,
Set.of(NET_CAPABILITY_OEM_PAID, NET_CAPABILITY_OEM_PRIVATE));
}
@@ -1155,13 +1158,14 @@
@SdkSuppress(minSdkVersion = 31, codeName = "S")
@Test
public void testConnectToOemPrivateSuggestion() throws Exception {
+ assertNotNull(sTestNetwork);
WifiNetworkSuggestion suggestion =
TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
- mTestNetwork)
+ sTestNetwork)
.setOemPrivate(true)
.build();
- mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
- mTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
+ sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
+ sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
}
/**
@@ -1173,13 +1177,14 @@
@SdkSuppress(minSdkVersion = 31, codeName = "S")
@Test
public void testConnectToOemPaidSuggestionFailure() throws Exception {
+ assertNotNull(sTestNetwork);
WifiNetworkSuggestion suggestion =
TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
- mTestNetwork)
+ sTestNetwork)
.setOemPaid(true)
.build();
- mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
- mTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
+ sNsNetworkCallback = sTestHelper.testConnectionFailureFlowWithSuggestion(
+ sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
}
/**
@@ -1191,13 +1196,14 @@
@SdkSuppress(minSdkVersion = 31, codeName = "S")
@Test
public void testConnectToOemPrivateSuggestionFailure() throws Exception {
+ assertNotNull(sTestNetwork);
WifiNetworkSuggestion suggestion =
TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
- mTestNetwork)
+ sTestNetwork)
.setOemPrivate(true)
.build();
- mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
- mTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
+ sNsNetworkCallback = sTestHelper.testConnectionFailureFlowWithSuggestion(
+ sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
}
/**
@@ -1209,12 +1215,13 @@
@SdkSuppress(minSdkVersion = 31, codeName = "S")
@Test
public void testConnectSuggestionFailureWithOemPaidNetCapability() throws Exception {
+ assertNotNull(sTestNetwork);
WifiNetworkSuggestion suggestion =
TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
- mTestNetwork)
+ sTestNetwork)
.build();
- mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
- mTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
+ sNsNetworkCallback = sTestHelper.testConnectionFailureFlowWithSuggestion(
+ sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
}
/**
@@ -1226,11 +1233,12 @@
@SdkSuppress(minSdkVersion = 31, codeName = "S")
@Test
public void testConnectSuggestionFailureWithOemPrivateNetCapability() throws Exception {
+ assertNotNull(sTestNetwork);
WifiNetworkSuggestion suggestion =
TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
- mTestNetwork)
+ sTestNetwork)
.build();
- mNsNetworkCallback = mTestHelper.testConnectionFailureFlowWithSuggestion(
- mTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
+ sNsNetworkCallback = sTestHelper.testConnectionFailureFlowWithSuggestion(
+ sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
}
}
diff --git a/tests/tests/wifi/src/android/net/wifi/p2p/cts/WifiP2pWfdInfoTest.java b/tests/tests/wifi/src/android/net/wifi/p2p/cts/WifiP2pWfdInfoTest.java
index ca68566..5ccc13d 100644
--- a/tests/tests/wifi/src/android/net/wifi/p2p/cts/WifiP2pWfdInfoTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/p2p/cts/WifiP2pWfdInfoTest.java
@@ -21,13 +21,18 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
+import android.content.Context;
+import android.net.wifi.cts.WifiFeature;
import android.net.wifi.cts.WifiJUnit4TestBase;
import android.net.wifi.p2p.WifiP2pWfdInfo;
import androidx.core.os.BuildCompat;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@@ -41,6 +46,13 @@
private final int TEST_MAX_THROUGHPUT = 1024;
private final boolean TEST_CONTENT_PROTECTION_SUPPORTED_STATUS = true;
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ assumeTrue(WifiFeature.isWifiSupported(context));
+ }
+
+ @Test
public void testWifiP2pWfdInfo() {
WifiP2pWfdInfo info = new WifiP2pWfdInfo();
@@ -61,6 +73,7 @@
copiedInfo.isContentProtectionSupported());
}
+ @Test
public void testWifiCoupledSink() {
assumeTrue(BuildCompat.isAtLeastS());
WifiP2pWfdInfo info = new WifiP2pWfdInfo();
@@ -74,11 +87,13 @@
assertTrue(info.isCoupledSinkSupportedAtSource());
}
+ @Test
public void testWifiP2pWfdR2Info() {
assumeTrue(BuildCompat.isAtLeastS());
WifiP2pWfdInfo info = new WifiP2pWfdInfo();
- info.setWfdR2Device(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE);
- assertTrue(info.isR2Enabled());
+ info.setR2DeviceType(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE);
+ assertEquals(WifiP2pWfdInfo.DEVICE_TYPE_WFD_SOURCE, info.getR2DeviceType());
+ assertTrue(info.isR2Supported());
}
}
diff --git a/tests/tests/wifi/src/android/net/wifi/passpoint/cts/PasspointConfigurationTest.java b/tests/tests/wifi/src/android/net/wifi/passpoint/cts/PasspointConfigurationTest.java
index c31925a..a6eebfe 100644
--- a/tests/tests/wifi/src/android/net/wifi/passpoint/cts/PasspointConfigurationTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/passpoint/cts/PasspointConfigurationTest.java
@@ -25,15 +25,14 @@
import android.net.wifi.hotspot2.pps.HomeSp;
import android.platform.test.annotations.AppModeFull;
-import androidx.core.os.BuildCompat;
import androidx.test.filters.SmallTest;
+import androidx.test.filters.SdkSuppress;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -181,10 +180,8 @@
/**
* Verify that the set and get decorated identity prefix methods work as expected.
*/
+ @SdkSuppress(minSdkVersion = 31, codeName = "S")
public void testSetGetDecoratedIdentityPrefix() throws Exception {
- if (!BuildCompat.isAtLeastS()) {
- return;
- }
PasspointConfiguration config = createConfig();
assertNull(config.getDecoratedIdentityPrefix());
config.setDecoratedIdentityPrefix(TEST_DECORATED_IDENTITY_PREFIX);
diff --git a/tests/translation/AndroidManifest.xml b/tests/translation/AndroidManifest.xml
index d1803bf..3ea50e9 100644
--- a/tests/translation/AndroidManifest.xml
+++ b/tests/translation/AndroidManifest.xml
@@ -37,6 +37,10 @@
<intent-filter>
<action android:name="android.service.translation.TranslationService"/>
</intent-filter>
+ <meta-data
+ android:name="android.translation_service"
+ android:resource="@xml/translation_config">
+ </meta-data>
</service>
<service android:name=".CtsContentCaptureService"
android:label="CtsContentCaptureService"
diff --git a/tests/translation/res/xml/translation_config.xml b/tests/translation/res/xml/translation_config.xml
new file mode 100644
index 0000000..6ab30d5
--- /dev/null
+++ b/tests/translation/res/xml/translation_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2021, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<translation-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="android.translation.cts.SimpleActivity">
+</translation-service>
\ No newline at end of file
diff --git a/tests/translation/src/android/translation/cts/CtsTranslationService.java b/tests/translation/src/android/translation/cts/CtsTranslationService.java
index 480a66b..33540b6 100644
--- a/tests/translation/src/android/translation/cts/CtsTranslationService.java
+++ b/tests/translation/src/android/translation/cts/CtsTranslationService.java
@@ -22,8 +22,9 @@
import android.os.HandlerThread;
import android.service.translation.TranslationService;
import android.util.Log;
+import android.view.translation.TranslationCapability;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationRequest;
-import android.view.translation.TranslationRequestValue;
import android.view.translation.TranslationResponse;
import android.view.translation.TranslationSpec;
@@ -34,12 +35,15 @@
import com.android.compatibility.common.util.Timeout;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
/**
* Implementation of {@link TranslationService} used in CTS tests.
@@ -99,8 +103,8 @@
}
@Override
- public void onCreateTranslationSession(@NonNull TranslationSpec sourceSpec,
- @NonNull TranslationSpec destSpec, int sessionId) {
+ public void onCreateTranslationSession(@NonNull TranslationContext translationContext,
+ int sessionId) {
Log.v(TAG, "onCreateTranslationSession");
}
@@ -120,6 +124,18 @@
request, sessionId, cancellationSignal, callback));
}
+ @Override
+ public void onTranslationCapabilitiesRequest(int sourceFormat, int targetFormat,
+ @NonNull Consumer<Set<TranslationCapability>> callback) {
+ //TODO: Implement properly with replier?
+ final HashSet<TranslationCapability> capabilities = new HashSet<>();
+ capabilities.add(new TranslationCapability(TranslationCapability.STATE_ON_DEVICE,
+ new TranslationSpec("en", sourceFormat),
+ new TranslationSpec("es", targetFormat),
+ /* uiTranslationEnabled= */ true, 0));
+ callback.accept(capabilities);
+ }
+
@NonNull
public static ServiceWatcher setServiceWatcher() {
if (sServiceWatcher != null) {
diff --git a/tests/translation/src/android/translation/cts/TranslationManagerTest.java b/tests/translation/src/android/translation/cts/TranslationManagerTest.java
index bd14874..38bd34f 100644
--- a/tests/translation/src/android/translation/cts/TranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/TranslationManagerTest.java
@@ -16,14 +16,19 @@
package android.translation.cts;
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.RESUMED;
import static com.google.common.truth.Truth.assertThat;
+import android.app.Application;
import android.app.Instrumentation;
+import android.app.PendingIntent;
import android.content.pm.PackageManager;
import android.platform.test.annotations.AppModeFull;
+import android.util.ArraySet;
import android.util.Log;
+import android.view.translation.TranslationCapability;
+import android.view.translation.TranslationContext;
import android.view.translation.TranslationManager;
import android.view.translation.TranslationRequest;
import android.view.translation.TranslationRequestValue;
@@ -32,10 +37,12 @@
import android.view.translation.TranslationSpec;
import android.view.translation.Translator;
-import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ActivitiesWatcher;
+import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
import com.android.compatibility.common.util.RequiredFeatureRule;
import com.android.compatibility.common.util.RequiredServiceRule;
@@ -44,13 +51,14 @@
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;
import java.util.Locale;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
/**
* Tests for {@link TranslationManager} related APIs.
@@ -76,6 +84,7 @@
private static final String TAG = "BasicTranslationTest";
private CtsTranslationService.ServiceWatcher mServiceWatcher;
+ private ActivitiesWatcher mActivitiesWatcher;
private static Instrumentation sInstrumentation;
private static CtsTranslationService.TranslationReplier sTranslationReplier;
@@ -94,6 +103,10 @@
@After
public void cleanup() {
Helper.resetTemporaryTranslationService();
+ if (mActivitiesWatcher != null) {
+ final Application app = (Application) ApplicationProvider.getApplicationContext();
+ app.unregisterActivityLifecycleCallbacks(mActivitiesWatcher);
+ }
}
@Test
@@ -114,40 +127,45 @@
final CountDownLatch translationLatch = new CountDownLatch(1);
final AtomicReference<TranslationResponse> responseRef = new AtomicReference<>();
- final Thread th = new Thread(() -> {
- final Translator translator = manager.createTranslator(
- new TranslationSpec(Locale.ENGLISH.getLanguage(),
- TranslationSpec.DATA_FORMAT_TEXT),
- new TranslationSpec(Locale.FRENCH.getLanguage(),
- TranslationSpec.DATA_FORMAT_TEXT));
- try {
- mServiceWatcher.waitOnConnected();
- } catch (InterruptedException e) {
- Log.w(TAG, "Exception waiting for onConnected");
+ final TranslationContext translationContext = new TranslationContext.Builder(
+ new TranslationSpec(Locale.ENGLISH.getLanguage(),
+ TranslationSpec.DATA_FORMAT_TEXT),
+ new TranslationSpec(Locale.FRENCH.getLanguage(),
+ TranslationSpec.DATA_FORMAT_TEXT))
+ .build();
+ final Translator translator = manager.createTranslator(translationContext);
+
+ try {
+ mServiceWatcher.waitOnConnected();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Exception waiting for onConnected");
+ }
+
+ assertThat(translator.isDestroyed()).isFalse();
+
+ final Consumer<TranslationResponse> callback = new Consumer<TranslationResponse>() {
+ @Override
+ public void accept(TranslationResponse translationResponse) {
+ responseRef.set(translationResponse);
+ translationLatch.countDown();
}
- assertThat(translator.isDestroyed()).isFalse();
+ };
- final TranslationResponse response = translator.translate(
- new TranslationRequest.Builder()
- .addTranslationRequestValue(
- TranslationRequestValue.forText("hello world"))
- .build());
+ translator.translate(new TranslationRequest.Builder()
+ .addTranslationRequestValue(TranslationRequestValue.forText("hello world"))
+ .build(), (r) -> r.run(), callback);
- sTranslationReplier.getNextTranslationRequest();
+ sTranslationReplier.getNextTranslationRequest();
- responseRef.set(response);
- translationLatch.countDown();
+ translator.destroy();
+ assertThat(translator.isDestroyed()).isTrue();
+ try {
+ mServiceWatcher.waitOnDisconnected();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Exception waiting for onDisconnected");
+ }
- translator.destroy();
- assertThat(translator.isDestroyed()).isTrue();
- try {
- mServiceWatcher.waitOnDisconnected();
- } catch (InterruptedException e) {
- Log.w(TAG, "Exception waiting for onDisconnected");
- }
- });
-
- th.start();
+ // Wait for translation to finish
translationLatch.await();
sTranslationReplier.assertNoUnhandledTranslationRequests();
@@ -157,6 +175,7 @@
assertThat(response).isNotNull();
assertThat(response.getTranslationStatus())
.isEqualTo(TranslationResponse.TRANSLATION_STATUS_SUCCESS);
+ assertThat(response.isFinalResponse()).isTrue();
assertThat(response.getTranslationResponseValues().size()).isEqualTo(1);
assertThat(response.getViewTranslationResponses().size()).isEqualTo(0);
@@ -168,27 +187,61 @@
}
@Test
- public void testGetSupportedLocales() throws Exception{
+ public void testGetTranslationCapabilities() throws Exception{
enableCtsTranslationService();
final TranslationManager manager = sInstrumentation.getContext().getSystemService(
TranslationManager.class);
final CountDownLatch latch = new CountDownLatch(1);
- final AtomicReference<List<String>> resultRef = new AtomicReference<>();
+ final AtomicReference<Set<TranslationCapability>> resultRef =
+ new AtomicReference<>();
final Thread th = new Thread(() -> {
- final List<String> supportLocales = manager.getSupportedLocales();
- // TODO(b/178651514): empty implementation not forward to service, should update tests
- resultRef.set(supportLocales);
+ final Set<TranslationCapability> capabilities =
+ manager.getTranslationCapabilities(TranslationSpec.DATA_FORMAT_TEXT,
+ TranslationSpec.DATA_FORMAT_TEXT);
+ resultRef.set(capabilities);
latch.countDown();
});
th.start();
latch.await();
- final List<String> supportLocales = resultRef.get();
- // TODO(b/178651514): empty implementation and has bug now. It will return null instead of
- // empty list
- assertThat(supportLocales).isNull();
+ final ArraySet<TranslationCapability> capabilities = new ArraySet<>(resultRef.get());
+ assertThat(capabilities.size()).isEqualTo(1);
+ capabilities.forEach((capability) -> {
+ assertThat(capability.getState()).isEqualTo(TranslationCapability.STATE_ON_DEVICE);
+
+ assertThat(capability.getSupportedTranslationFlags()).isEqualTo(0);
+ assertThat(capability.isUiTranslationEnabled()).isTrue();
+ assertThat(capability.getSourceSpec().getLanguage()).isEqualTo("en");
+ assertThat(capability.getSourceSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+ assertThat(capability.getTargetSpec().getLanguage()).isEqualTo("es");
+ assertThat(capability.getTargetSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+ });
+ }
+
+ @Test
+ public void testGetTranslationSettingsActivityIntent() throws Exception{
+ enableCtsTranslationService();
+
+ final TranslationManager manager = sInstrumentation.getContext().getSystemService(
+ TranslationManager.class);
+ final PendingIntent pendingIntent = manager.getTranslationSettingsActivityIntent();
+
+ assertThat(pendingIntent).isNotNull();
+ assertThat(pendingIntent.isImmutable()).isTrue();
+
+ // Start Settings Activity and verify if the expected Activity resumed
+ mActivitiesWatcher = new ActivitiesWatcher(5_000);
+ final Application app = (Application) ApplicationProvider.getApplicationContext();
+ app.registerActivityLifecycleCallbacks(mActivitiesWatcher);
+ final ActivityWatcher watcher = mActivitiesWatcher.watch(SimpleActivity.class);
+
+ pendingIntent.send();
+
+ watcher.waitFor(RESUMED);
}
protected void enableCtsTranslationService() {
diff --git a/tests/translation/src/android/translation/cts/unittests/TranslationCapabilityTest.java b/tests/translation/src/android/translation/cts/unittests/TranslationCapabilityTest.java
new file mode 100644
index 0000000..ef00074
--- /dev/null
+++ b/tests/translation/src/android/translation/cts/unittests/TranslationCapabilityTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.translation.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.view.translation.TranslationCapability;
+import android.view.translation.TranslationContext;
+import android.view.translation.TranslationSpec;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class TranslationCapabilityTest {
+
+ private final TranslationSpec sourceSpec =
+ new TranslationSpec("en", TranslationSpec.DATA_FORMAT_TEXT);
+ private final TranslationSpec targetSpec =
+ new TranslationSpec("es", TranslationSpec.DATA_FORMAT_TEXT);
+
+ @Test
+ public void testCapability_nullSpecs() {
+ assertThrows(NullPointerException.class,
+ () -> new TranslationCapability(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD,
+ null, targetSpec, /* uiTranslationEnabled= */ true,
+ /* supportedTranslationFlags= */ 0));
+ assertThrows(NullPointerException.class,
+ () -> new TranslationCapability(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD,
+ sourceSpec, null,/* uiTranslationEnabled= */ true,
+ /* supportedTranslationFlags= */ 0));
+ }
+
+ @Test
+ public void testCapability_validCapability() {
+ final TranslationCapability capability =
+ new TranslationCapability(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD,
+ sourceSpec, targetSpec,/* uiTranslationEnabled= */ true,
+ TranslationContext.FLAG_TRANSLITERATION);
+
+ assertThat(capability.getState())
+ .isEqualTo(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD);
+ assertThat(capability.getSupportedTranslationFlags())
+ .isEqualTo(TranslationContext.FLAG_TRANSLITERATION);
+ assertThat(capability.isUiTranslationEnabled()).isTrue();
+
+ assertThat(capability.getSourceSpec().getLanguage()).isEqualTo("en");
+ assertThat(capability.getSourceSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+ assertThat(capability.getTargetSpec().getLanguage()).isEqualTo("es");
+ assertThat(capability.getTargetSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+ }
+
+ @Test
+ public void testParceledCapability() {
+ final TranslationCapability capability =
+ new TranslationCapability(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD,
+ sourceSpec, targetSpec,/* uiTranslationEnabled= */ true,
+ TranslationContext.FLAG_TRANSLITERATION);
+
+ assertThat(capability.getState())
+ .isEqualTo(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD);
+ assertThat(capability.getSupportedTranslationFlags())
+ .isEqualTo(TranslationContext.FLAG_TRANSLITERATION);
+ assertThat(capability.isUiTranslationEnabled()).isTrue();
+
+ assertThat(capability.getSourceSpec().getLanguage()).isEqualTo("en");
+ assertThat(capability.getSourceSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+ assertThat(capability.getTargetSpec().getLanguage()).isEqualTo("es");
+ assertThat(capability.getTargetSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+ final Parcel parcel = Parcel.obtain();
+ capability.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ final TranslationCapability parceledCapability =
+ TranslationCapability.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ assertThat(parceledCapability.getState())
+ .isEqualTo(TranslationCapability.STATE_AVAILABLE_TO_DOWNLOAD);
+ assertThat(parceledCapability.getSupportedTranslationFlags())
+ .isEqualTo(TranslationContext.FLAG_TRANSLITERATION);
+ assertThat(parceledCapability.isUiTranslationEnabled()).isTrue();
+
+ assertThat(parceledCapability.getSourceSpec().getLanguage()).isEqualTo("en");
+ assertThat(parceledCapability.getSourceSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+ assertThat(parceledCapability.getTargetSpec().getLanguage()).isEqualTo("es");
+ assertThat(parceledCapability.getTargetSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+ }
+}
diff --git a/tests/translation/src/android/translation/cts/unittests/TranslationContextTest.java b/tests/translation/src/android/translation/cts/unittests/TranslationContextTest.java
new file mode 100644
index 0000000..415f00f
--- /dev/null
+++ b/tests/translation/src/android/translation/cts/unittests/TranslationContextTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.translation.cts.unittests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.view.translation.TranslationContext;
+import android.view.translation.TranslationSpec;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class TranslationContextTest {
+
+ private final TranslationSpec sourceSpec =
+ new TranslationSpec("en", TranslationSpec.DATA_FORMAT_TEXT);
+ private final TranslationSpec targetSpec =
+ new TranslationSpec("es", TranslationSpec.DATA_FORMAT_TEXT);
+
+ @Test
+ public void testContext_nullSpecs() {
+ assertThrows(NullPointerException.class,
+ () -> new TranslationContext.Builder(null, targetSpec));
+ assertThrows(NullPointerException.class,
+ () -> new TranslationContext.Builder(sourceSpec, null));
+ }
+
+ @Test
+ public void testContext_validContext() {
+ final TranslationContext context =
+ new TranslationContext.Builder(sourceSpec, targetSpec)
+ .setTranslationFlags(TranslationContext.FLAG_DICTIONARY_DESCRIPTION)
+ .build();
+
+ assertThat(context.getTranslationFlags())
+ .isEqualTo(TranslationContext.FLAG_DICTIONARY_DESCRIPTION);
+
+ assertThat(context.getSourceSpec().getLanguage()).isEqualTo("en");
+ assertThat(context.getSourceSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+ assertThat(context.getTargetSpec().getLanguage()).isEqualTo("es");
+ assertThat(context.getTargetSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+ }
+
+ @Test
+ public void testParceledContext() {
+ final TranslationContext context = new TranslationContext.Builder(sourceSpec, targetSpec)
+ .build();
+
+ assertThat(context.getTranslationFlags()).isEqualTo(0);
+
+ assertThat(context.getSourceSpec().getLanguage()).isEqualTo("en");
+ assertThat(context.getSourceSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+ assertThat(context.getTargetSpec().getLanguage()).isEqualTo("es");
+ assertThat(context.getTargetSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+ final Parcel parcel = Parcel.obtain();
+ context.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ final TranslationContext parceledContext =
+ TranslationContext.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ assertThat(parceledContext.getTranslationFlags()).isEqualTo(0);
+
+ assertThat(parceledContext.getSourceSpec().getLanguage()).isEqualTo("en");
+ assertThat(parceledContext.getSourceSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+
+ assertThat(parceledContext.getTargetSpec().getLanguage()).isEqualTo("es");
+ assertThat(parceledContext.getTargetSpec().getDataFormat())
+ .isEqualTo(TranslationSpec.DATA_FORMAT_TEXT);
+ }
+}
diff --git a/tests/translation/src/android/translation/cts/unittests/TranslationResponseTest.java b/tests/translation/src/android/translation/cts/unittests/TranslationResponseTest.java
index d7f0054..914e9f8 100644
--- a/tests/translation/src/android/translation/cts/unittests/TranslationResponseTest.java
+++ b/tests/translation/src/android/translation/cts/unittests/TranslationResponseTest.java
@@ -20,8 +20,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.testng.Assert.assertThrows;
-
import android.os.Parcel;
import android.view.autofill.AutofillId;
import android.view.translation.TranslationResponse;
@@ -41,7 +39,7 @@
.setText("hello")
.build();
- private final ViewTranslationResponse mResponse = new ViewTranslationResponse
+ private final ViewTranslationResponse mViewResponse = new ViewTranslationResponse
.Builder(new AutofillId(17))
.setValue("sample id",
new TranslationResponseValue.Builder(STATUS_SUCCESS)
@@ -51,24 +49,25 @@
@Test
public void testBuilder_validViewTranslationResponse() {
- final TranslationResponse request =
+ final TranslationResponse response =
new TranslationResponse.Builder(TranslationResponse.TRANSLATION_STATUS_SUCCESS)
- .setViewTranslationResponse(0, mResponse)
+ .setViewTranslationResponse(0, mViewResponse)
.build();
- assertThat(request.getTranslationResponseValues().size()).isEqualTo(0);
- assertThat(request.getViewTranslationResponses().size()).isEqualTo(1);
+ assertThat(response.isFinalResponse()).isTrue();
+ assertThat(response.getTranslationResponseValues().size()).isEqualTo(0);
+ assertThat(response.getViewTranslationResponses().size()).isEqualTo(1);
- final ViewTranslationResponse viewRequest =
- request.getViewTranslationResponses().get(0);
- assertThat(viewRequest.getAutofillId()).isEqualTo(new AutofillId(17));
- assertThat(viewRequest.getKeys().size()).isEqualTo(1);
- assertThat(viewRequest.getValue("sample id").getText()).isEqualTo("sample text");
+ final ViewTranslationResponse viewResponse =
+ response.getViewTranslationResponses().get(0);
+ assertThat(viewResponse.getAutofillId()).isEqualTo(new AutofillId(17));
+ assertThat(viewResponse.getKeys().size()).isEqualTo(1);
+ assertThat(viewResponse.getValue("sample id").getText()).isEqualTo("sample text");
}
@Test
public void testBuilder_errorViewTranslationResponse() {
- final TranslationResponse request =
+ final TranslationResponse response =
new TranslationResponse.Builder(TranslationResponse.TRANSLATION_STATUS_SUCCESS)
.setViewTranslationResponse(0, new ViewTranslationResponse
.Builder(new AutofillId(42))
@@ -77,35 +76,38 @@
.build())
.build();
- assertThat(request.getTranslationResponseValues().size()).isEqualTo(0);
- assertThat(request.getViewTranslationResponses().size()).isEqualTo(1);
+ assertThat(response.isFinalResponse()).isTrue();
+ assertThat(response.getTranslationResponseValues().size()).isEqualTo(0);
+ assertThat(response.getViewTranslationResponses().size()).isEqualTo(1);
- final ViewTranslationResponse viewRequest =
- request.getViewTranslationResponses().get(0);
- assertThat(viewRequest.getAutofillId()).isEqualTo(new AutofillId(42));
- assertThat(viewRequest.getKeys().size()).isEqualTo(1);
- assertThat(viewRequest.getValue("id2").getStatusCode())
+ final ViewTranslationResponse viewResponse =
+ response.getViewTranslationResponses().get(0);
+ assertThat(viewResponse.getAutofillId()).isEqualTo(new AutofillId(42));
+ assertThat(viewResponse.getKeys().size()).isEqualTo(1);
+ assertThat(viewResponse.getValue("id2").getStatusCode())
.isEqualTo(TranslationResponseValue.STATUS_ERROR);
}
@Test
public void testBuilder_validTranslationResponseValue() {
- final TranslationResponse request =
+ final TranslationResponse response =
new TranslationResponse.Builder(TranslationResponse.TRANSLATION_STATUS_SUCCESS)
+ .setFinalResponse(false)
.setTranslationResponseValue(0, mValue)
.build();
- assertThat(request.getTranslationResponseValues().size()).isEqualTo(1);
- assertThat(request.getViewTranslationResponses().size()).isEqualTo(0);
+ assertThat(response.isFinalResponse()).isFalse();
+ assertThat(response.getTranslationResponseValues().size()).isEqualTo(1);
+ assertThat(response.getViewTranslationResponses().size()).isEqualTo(0);
final TranslationResponseValue value =
- request.getTranslationResponseValues().get(0);
+ response.getTranslationResponseValues().get(0);
assertThat(value.getText()).isEqualTo("hello");
}
@Test
- public void testParceledRequest_validTranslationResponseValues() {
- final TranslationResponse request =
+ public void testParceledResponse_validTranslationResponseValues() {
+ final TranslationResponse response =
new TranslationResponse.Builder(TranslationResponse.TRANSLATION_STATUS_SUCCESS)
.setTranslationResponseValue(0, mValue)
.setTranslationResponseValue(2,
@@ -115,20 +117,21 @@
.build();
final Parcel parcel = Parcel.obtain();
- request.writeToParcel(parcel, 0);
+ response.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
- final TranslationResponse parceledRequest =
+ final TranslationResponse parceledResponse =
TranslationResponse.CREATOR.createFromParcel(parcel);
- assertThat(parceledRequest.getTranslationResponseValues().size()).isEqualTo(2);
- assertThat(parceledRequest.getViewTranslationResponses().size()).isEqualTo(0);
+ assertThat(parceledResponse.isFinalResponse()).isTrue();
+ assertThat(parceledResponse.getTranslationResponseValues().size()).isEqualTo(2);
+ assertThat(parceledResponse.getViewTranslationResponses().size()).isEqualTo(0);
final TranslationResponseValue value1 =
- parceledRequest.getTranslationResponseValues().get(0);
+ parceledResponse.getTranslationResponseValues().get(0);
assertThat(value1.getText()).isEqualTo("hello");
final TranslationResponseValue value2 =
- parceledRequest.getTranslationResponseValues().get(2);
+ parceledResponse.getTranslationResponseValues().get(2);
assertThat(value2.getText()).isEqualTo("world");
}
@@ -136,10 +139,11 @@
public void testBuilder_mixingAdders() {
final TranslationResponse response =
new TranslationResponse.Builder(TranslationResponse.TRANSLATION_STATUS_SUCCESS)
- .setViewTranslationResponse(0, mResponse)
+ .setViewTranslationResponse(0, mViewResponse)
.setTranslationResponseValue(0, mValue)
.build();
+ assertThat(response.isFinalResponse()).isTrue();
assertThat(response.getTranslationResponseValues().size()).isEqualTo(1);
assertThat(response.getViewTranslationResponses().size()).isEqualTo(1);
@@ -155,10 +159,10 @@
}
@Test
- public void testParceledRequest_validViewTranslationResponses() {
- final TranslationResponse request =
+ public void testParceledResponse_validViewTranslationResponses() {
+ final TranslationResponse response =
new TranslationResponse.Builder(TranslationResponse.TRANSLATION_STATUS_SUCCESS)
- .setViewTranslationResponse(0, mResponse)
+ .setViewTranslationResponse(0, mViewResponse)
.setViewTranslationResponse(2, new ViewTranslationResponse
.Builder(new AutofillId(42))
.setValue("id2",
@@ -169,25 +173,26 @@
.build();
final Parcel parcel = Parcel.obtain();
- request.writeToParcel(parcel, 0);
+ response.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
- final TranslationResponse parceledRequest =
+ final TranslationResponse parceledResponse =
TranslationResponse.CREATOR.createFromParcel(parcel);
- assertThat(parceledRequest.getTranslationResponseValues().size()).isEqualTo(0);
- assertThat(parceledRequest.getViewTranslationResponses().size()).isEqualTo(2);
+ assertThat(parceledResponse.isFinalResponse()).isTrue();
+ assertThat(parceledResponse.getTranslationResponseValues().size()).isEqualTo(0);
+ assertThat(parceledResponse.getViewTranslationResponses().size()).isEqualTo(2);
- final ViewTranslationResponse request1 =
- parceledRequest.getViewTranslationResponses().get(0);
- assertThat(request1.getAutofillId()).isEqualTo(new AutofillId(17));
- assertThat(request1.getKeys().size()).isEqualTo(1);
- assertThat(request1.getValue("sample id").getText()).isEqualTo("sample text");
+ final ViewTranslationResponse viewResponse1 =
+ parceledResponse.getViewTranslationResponses().get(0);
+ assertThat(viewResponse1.getAutofillId()).isEqualTo(new AutofillId(17));
+ assertThat(viewResponse1.getKeys().size()).isEqualTo(1);
+ assertThat(viewResponse1.getValue("sample id").getText()).isEqualTo("sample text");
- final ViewTranslationResponse request2 =
- parceledRequest.getViewTranslationResponses().get(2);
- assertThat(request2.getAutofillId()).isEqualTo(new AutofillId(42));
- assertThat(request2.getKeys().size()).isEqualTo(1);
- assertThat(request2.getValue("id2").getText()).isEqualTo("test");
+ final ViewTranslationResponse viewResponse2 =
+ parceledResponse.getViewTranslationResponses().get(2);
+ assertThat(viewResponse2.getAutofillId()).isEqualTo(new AutofillId(42));
+ assertThat(viewResponse2.getKeys().size()).isEqualTo(1);
+ assertThat(viewResponse2.getValue("id2").getText()).isEqualTo("test");
}
}
\ No newline at end of file
diff --git a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
index 930bc4d..1323093 100644
--- a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
+++ b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
@@ -53,4 +53,10 @@
<!-- No SettingsIntelligence -->
<option name="compatibility:exclude-filter" value="CtsContentTestCases android.content.cts.AvailableIntentsTest#testSettingsSearchIntent" />
+ <!-- No AccessibilityService -->
+ <option name="compatibility:exclude-filter" value="CtsAccessibilityServiceTestCases android.accessibilityservice.cts" />
+
+ <!-- No Statsd -->
+ <option name="compatibility:exclude-filter" value="CtsStatsdHostTestCases" />
+
</configuration>